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

时间:2019-09-02 20:21:04

标签: c# .net reflection system.reflection

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

CanReadCanWrite仅基于派生类型返回值,而且GetMethodSetMethod不包含基本类型的方法。

但是,在编写代码时,可以使用基本类型的访问器(这样我们就可以仅使用派生类型中定义的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);
    }
}

我希望最后两项测试能够通过,我缺少什么?

2 个答案:

答案 0 :(得分:4)

  

我希望最后两项测试能够通过,我缺少什么?

要确定的答案,您必须问起最初设计.NET及其类型系统的人员。那就是……

在我看来,这与提供有关如何编写类型的信息的反射目标是一致的。考虑另一种选择:如果返回的PropertyInfo对象同时包含派生类的setter和基类的getter。从返回的结果中了解实际上在何处声明的内容将变得更加困难,并且PropertyInfo对象本身可能会不一致。这是因为存在PropertyInfo.DeclaringType属性,该属性表示该成员的所有信息仅与该声明类型有关。

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

DeclaringType带有属性,可告诉您属性在哪个类中声明。然后SetMethodGetMethod属性告诉您该类声明了什么

恕我直言,这使反射API更简单,更一致且更易于理解。这确实意味着您需要做更多的工作来分析虚拟属性。但是,思考总是会涉及“更多的工作”。 :)

答案 1 :(得分:2)

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

如果PropertyInfo具有类似GetBaseDefinition()but it does not(还有this thread)的内容,那么会更容易,因此我们必须使用accessor方法。如果访问器的方法信息具有对属性信息but it does not的引用,也将更加容易,因此我们遍历 all 属性并假定存在完全匹配的内容。

这是一个幼稚的解决方案:

// 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);
}

它与您的true返回PropertiesReflectionTests.Property,因此在这种情况下可以使用。我想,应对每种情况都需要加倍小心。

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

可以编写类似的方法CanWriteExt