【问题标题】:Strange Effect with Overridden Properties and Reflection具有覆盖属性和反射的奇怪效果
【发布时间】:2011-12-29 17:32:08
【问题描述】:

我在 .NET/Reflection 中遇到了一个奇怪的行为,找不到任何解决方案/解释:

class A 
{
   public virtual string TestString { get; set; }
}

class B : A
{
   public override string TestString
   {
      get { return "x"; }
   }
}

因为属性只是成对的函数(get_PropName()set_PropName()),只覆盖“get”部分应该保留“set”部分,因为它在基类中。如果您尝试实例化 B 类并将值分配给 TestString,就会发生这种情况,它使用 A 类的实现。

但是如果我在反射中查看 B 类的实例化对象会发生什么:

PropertyInfo propInfo = b.GetType().GetProperty("TestString");
propInfo.CanRead  ---> true
propInfo.CanWrite ---> false(!)

如果我尝试从反射调用 setter:

propInfo.SetValue("test", b, null);

我什至会收到带有以下消息的ArgumentException

找不到属性集方法。

这符合预期吗?因为我似乎没有为 GetProperty() 方法找到 BindingFlags 的组合,该方法将属性与来自反射的有效 get/set 对返回给我。

编辑: 如果我在GetProperties() 上使用BindingFlags.DeclaredOnly,我会期望这种行为,但默认值 (BindingFlags.Default) 会考虑继承的成员,并且 TestString 的设置器显然是继承的!

【问题讨论】:

  • 首先你错过了 A 类 oroperty 上的“虚拟”。然后,您已经覆盖了一个属性,该属性现在在 B 中不再具有设置器。我发现绑定标志不允许在只读属性上设置非常直观。
  • @Marino:我认为 OP 的观点是你可以合法地做 var b = new B(); b.TestString = "foo"; 但反射会告诉你 B.TestString 没有二传手。在这方面,GetProperty 的行为似乎与 GetMethod 等不同。
  • @LukeH 我无法做到var b = new B(); b.TestString = "foo";。它告诉我(正确地)属性 TestString 无法分配,因为它是只读的。我可以A b = new B(); b.TestString = "foo";,但那是因为我将B 存储为A,而A 可以设置TestString 属性。它实际上并没有做任何事情,因为在获取值时,它读取的是B.get_TestString(),而不是A.get_TestString()
  • @LukeH 我复制/粘贴了原始问题中的类定义,除了我将virtual 添加到TestString 属性定义中,因为否则它不会编译。是的,我确信它不会编译 :) 如果这很重要,我正在使用 .Net 4.0、VS 2010 和 Windows 7。
  • 在 LinqPad 中为我编译。 nopaste.info/43c3ee3bde_nl.html 但正如预期的那样,如果我删除 override,它不会编译,隐藏而不是覆盖属性。

标签: c# .net reflection properties overriding


【解决方案1】:

这里有一个解决方法:

typeof(B).GetProperty("TestString")
         .GetAccessors()            // { B.get_TestString() }
         .First()                   // B.get_TestString()
         .GetBaseDefinition()       // A.get_TestString()
         .DeclaringType             // typeof(A)
         .GetProperty("TestString") // A.TestString: CanRead and CanWrite

这种方法应该相当稳健。如果您正在寻找非公共访问器,则需要更加小心 (BindingFlags)。

编辑:

请注意,此方法不同于“硬编码”typeof(A).GetProperty("TestString")typeof(B).BaseType.GetProperty("TestString"),因为它会找到声明相关属性的实际 原始 类型。由于派生类型不可能(至少在 C# 中)添加新的访问器到被覆盖的属性,所以这个“原始”类型的属性声明应该包含所有相关的访问器。

【讨论】:

  • 我不太明白这里的意思,这不就是typeof(A).GetProperty("TestString")typeof(B).BaseType.GetProperty("TestString")的迂回说法吗?
  • @H.B.:我试图通过编辑来解决您的问题。
  • 这无济于事,因为您无法调用 propInfo.SetValue("test", b, null); -
  • 不错的方法 Ani,但我并不是真的在寻找解决方法,而是在寻找 .NET 反射行为是否真的正确的解释。属性只是运行时的方法,应该遵循适用于方法的继承规则——这就是它们的作用,除了反射。我没有向该属性添加新定义,也没有删除它们,我只是覆盖了 GETTER (get_TestString()) 并选择不覆盖 SETTER (set_TestString())。这就是代码中发生的事情,但是反射告诉我这个属性不再有设置器了。这是错误的。
【解决方案2】:

你不是在重写一个方法,你是在重写一个属性定义

属性的默认定义包括Get/Set方法,而你的新定义只包括Get方法,所以你的覆盖属性只有Get可用,而不是Set是有意义的

编辑

如果你在上面运行反射器之类的东西,你会看到

class A 
{
   public virtual string TestString { get; set; }
}

class B : A
{
   public override string TestString
   {
      get { return "x"; }
   }
}

编译成类似的样子

internal class A
{
    // Fields
    [CompilerGenerated]
    private string <TestString>k__BackingField;

    // Methods
    public A();

    // Properties
    public virtual string TestString { [CompilerGenerated] get; [CompilerGenerated] set; }
}

internal class B : A
{
    // Methods
    public B();

    // Properties
    public override string TestString { get; }
}

当你在代码中设置值时,你实际上是在调用类似B.base.set_TestValue 的东西。当你反映一些东西时,你试图找到不存在的B.set_TestValue

虽然您不能覆盖属性,但您可以覆盖属性定义(只要它不与基本属性定义冲突)。由于您的问题最初是用 WPF 标记的,因此我当时正在考虑 DependencyProperties,它们实际上是属性定义,而不是您可能想到的某种意义上的属性。

【讨论】:

  • 对不起,您的解释对我来说没有多大意义。您不能覆盖定义,只能覆盖功能。如果属性转换为 get_* 和 set_* 方法对,那么所有继承规则都应该适用于属性,就像它们适用于方法一样。除了使用反射查看属性时,这就是发生的情况。然后行为是完全出乎意料和奇怪的(至少对我来说)。因为从简单的代码来看,Setter 仍然存在并按预期工作。
  • 感谢您的回答。所以要么我对反射的元级别完全错误,要么这只是实现得很糟糕......反射是否告诉我特定对象在源代码级别是如何设计的,或者它在运行时看起来像“刚刚” ?因为在运行时,名为“TestString”的属性有一个getter(覆盖)和一个setter(继承)。 GetProperties() 为我提供了对象的所有属性,包括继承的属性。是的,它不是在“源代码”级别上继承的,但在实际运行时,它是 - 无论编译器最终如何处理它。
  • @naacal 我最好的猜测是,Reflection 采用了它所涉及的第一个属性定义,并且由于该属性是在 B 上定义的,并且仅使用 Get 方法,因此它不会费心查看基础class 查看还有哪些其他定义,因为它看到 class B 有一些东西覆盖了 class A 的内容。如果从 B 中删除 TestString 属性,您将看到反射返回 TestString 的 A 定义
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-09
  • 1970-01-01
  • 1970-01-01
  • 2020-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多