奇怪的效果与覆盖属性和反射

本文关键字:覆盖 属性 反射 | 更新日期: 2023-09-27 18:14:58

我在.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与以下消息:

没有找到属性设置方法。

是否符合预期?因为我似乎没有找到BindingFlagsGetProperty()方法的组合,该方法从反射中返回具有工作get/set对的属性。

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

奇怪的效果与覆盖属性和反射

这是一个解决方法:

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

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

编辑:

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

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

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

编辑

如果你在上面运行类似Reflector的命令,你会看到

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,这实际上是属性定义,而不是您可能想到的意义上的属性。