【问题标题】:Why do CanRead and CanWrite return false in C# for properties with overridden accessors?为什么 CanRead 和 CanWrite 在 C# 中为具有覆盖访问器的属性返回 false?
【发布时间】:2019-09-02 20:21:04
【问题描述】:

当尝试从派生属性或使用 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);
    }
}

我希望最后两个测试通过,我错过了什么?

【问题讨论】:

  • 如果你不指定getter,那么显然Property.GetMethod会返回null。你期待什么?
  • @haim770 该属性已被覆盖,我可以从中读取一个值(因为它在基类中有一个 getter)。但是我不能通过反射得到这个get方法。这就是困扰我的地方。
  • @haim 我更新了示例并包括通过派生类型的对象从该属性读取的通过测试。

标签: c# .net reflection system.reflection


【解决方案1】:

我希望最后两个测试通过,我错过了什么?

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

在我看来,这与反射提供有关如何编写类型的信息的目标是一致的。考虑另一种选择:如果返回的PropertyInfo 对象同时包含派生类的setter 和基类的getter 会怎样。从返回的结果中理解实际在哪里声明的内容要困难得多,而且PropertyInfo 对象本身可以说是不一致的。这是因为 PropertyInfo.DeclaringType 属性意味着该成员的所有信息都只属于该声明类型。

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

对于属性,DeclaringType 告诉您 property 是在哪个类中声明的。然后SetMethodGetMethod 属性告诉你那个 类声明了什么。

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

【讨论】:

  • 你说的很有道理!此处另一个答案的 cmets 中提到的另一个有趣的部分是使用 sealed override 使测试通过。但是,是否有任何系统内置方法可以可靠地从虚拟属性中获取所有访问器?
  • "是否有任何系统内置方法可以可靠地从虚拟属性中获取所有访问器?" -- 只需一次调用?我不这么认为。但是通过类型层次结构来识别相关的访问器并不难。我同意使用sealed 改变访问器的表示方式很有趣;请注意,当您执行此操作时,getter 的 DeclaringType派生 类型,而不是实际声明 getter 的基本类型。如果是类型系统方面的专家,我可能会确切地知道这是为什么;我认为这与 sealed 对类型所做的更改有关。
  • 我同意这里提供的答案,甚至是“询问设计师”,我认为这无论如何都属于“现在不能改变”类型的回应。我认为这里的命名是有缺陷的,但是,“CanRead”可能应该被命名为“HasGetter”或类似名称,因为 PropertyInfo 对象显示为 false,即使您非常清楚地可以读取该属性。但即使他们同意,他们现在几乎肯定不会改变这一点。
  • 在我的(新)回答中,我尝试做你最后提到的“多做一点工作”。
  • @JeppeStigNielsen @Peter 我已经发布了一个关于sealed overridenew question,也许我们会在那里得到详细的解释。
【解决方案2】:

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

如果PropertyInfoGetBaseDefinition()but it does not(也有this thread)之类的东西会更容易,所以我们必须通过访问器方法。如果访问器的方法信息有对属性信息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);
}

它会返回 true 和您的 PropertiesReflectionTests.Property,因此它适用于这种情况。我想,处理每一个案件都需要更加小心。

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

可以写一个类似的方法CanWriteExt

【讨论】:

    猜你喜欢
    • 2015-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多