为什么 CanRead 和 CanWrite return 在 C# 中对于具有覆盖访问器的属性为 false?

Why do CanRead and CanWrite return false in C# for properties with overridden accessors?

当尝试从派生属性获取属性访问器或使用 CanRead / CanWrite 时,由于某些原因未考虑基本自动属性。

CanReadCanWrite return 值仅基于派生类型,而且 GetMethodSetMethod 不包含来自基类型的方法。

然而,当从基类型编写代码访问器时可以使用(这样我们就可以读取重写的 auto-属性,只有 setter 在派生类型中定义)。

这是作为单元测试编写的重现它的代码:

using System.Reflection;
using NUnit.Framework;

[TestFixture]
public class PropertiesReflectionTests
{
    public class WithAutoProperty
    {
        public virtual object Property { get; set; }
    }

    public class OverridesOnlySetter : WithAutoProperty
    {
        public override object Property
        {
            set => base.Property = value;
        }
    }

    private static readonly PropertyInfo Property = typeof(OverridesOnlySetter).GetProperty(nameof(OverridesOnlySetter.Property));

    // This one is passing
    [Test]
    public void Property_ShouldBeReadable()
    {
        var overridesOnlySetter = new OverridesOnlySetter {Property = "test"};

        Assert.AreEqual(overridesOnlySetter.Property, "test");
    }

    // This one is failing
    [Test]
    public void CanRead_ShouldBeTrue()
    {
        Assert.True(Property.CanRead);
    }

    // And this is failing too
    [Test]
    public void GetMethod_ShouldBeNotNull()
    {
        Assert.NotNull(Property.GetMethod);
    }
}

我预计最后两个测试会通过,我错过了什么?

I expected last two tests to pass, what am I missing?

要获得明确的答案,您必须询问最初设计 .NET 及其类型系统的人员。也就是说……

在我看来,这与反射提供有关类型编写方式的信息的目标是一致的。考虑替代方案:如果返回的 PropertyInfo 对象同时包含来自派生 class 的 setter 和来自基础 class 的 getter 怎么办?从返回的结果中很难理解实际声明的位置,并且 PropertyInfo 对象本身可能会不一致。这是因为存在 PropertyInfo.DeclaringType 属性,这意味着成员的所有信息都只属于该声明类型。

对于既不是属性也不是事件的成员(两者都封装了 class 成员的 ),您将获得预期的行为。当然除非你传递 BindingFlags.DeclaredOnly,这将返回的信息限制为声明类型。但是对于那些类型的成员,DeclaringType 属性 明确地告诉您成员实际声明的类型。

对于 属性,DeclaringType 告诉您 class 中声明了 属性。然后 SetMethodGetMethod 属性告诉您 that class 声明的内容。

恕我直言,这使得反射 API 更简单、更一致且更容易理解。这确实意味着你必须做更多的工作来分析虚拟财产。但是,反思总是会涉及 "a little more work"。 :)

正如 Peter Duniho 在他的回答中所解释的那样,这似乎需要一些工作。

如果 PropertyInfo 有类似 GetBaseDefinition()but it does not (also this thread), so we have to go through the accessor method. It would also be easier if the method info for the accessor had a reference back to the property info, but it does not 的东西会更容易,所以我们 运行 通过 所有 属性并假设完全匹配。

所以这是一个天真的解决方案:

// does not necessarily work as expected if the property or one of its accessors
// (getter or setter) is not public
internal static bool CanReadExt(PropertyInfo pi)
{
  if (pi.CanRead)
    return true;

  // assume we have a setter since we do not have a getter
  var setter = pi.SetMethod
    ?? throw new Exception("Neither getter nor setter in property?");

  // try to acquire setter of base property
  var baseSetter = setter.GetBaseDefinition();

  // if the property was not overridden, we can return
  if (setter.DeclaringType == baseSetter.DeclaringType)
    return false;

  // try to find the base property
  var basePi = baseSetter.DeclaringType.GetProperties()
    .SingleOrDefault(x => x.SetMethod == baseSetter)
    ?? throw new Exception("Set accessor was overridden but we could not find property info for base property.");

  // recursively call ourselves
  return CanReadExt(basePi);
}

它 returns true 与你的 PropertiesReflectionTests.Property,所以它在那种情况下有效。我想需要更加小心地处理每一个案例。

如果您愿意,可以将此方法作为扩展方法。

可以写一个类似的方法CanWriteExt