Странный эффект с переопределенными свойствами и отражением

Я столкнулся с странным поведением в .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", должны оставить "установленную" часть так, как она есть в базовом классе. И это именно то, что произойдет, если вы попробуете инициализировать класс B и присвоить значение TestString, он использует реализацию класса A.

Но что произойдет, если я посмотрю на объект-объект класса B в отражении, это:

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

И если я попытаюсь вызвать сеттера из отражения с помощью:

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

Я даже получу ArgumentException со следующим сообщением:

Метод набора свойств не найден.

Это так, как ожидалось? Поскольку мне кажется, что для метода GetProperty() я не нашел комбинацию BindingFlags, которая возвращает мне свойство с помощью рабочей пары get/set из отражения.

EDIT: Я бы ожидал такого поведения, если бы использовал BindingFlags.DeclaredOnly в GetProperties(), но по умолчанию (BindingFlags.Default) учитываются унаследованные члены, и наборщик TestString явно наследуется!

Ответ 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), если вы ищете непубличный аксессор (ы).

EDIT:

Обратите внимание, что этот подход отличается от "hardcoding" typeof(A).GetProperty("TestString") или typeof(B).BaseType.GetProperty("TestString"), потому что он находит фактический оригинальный тип, объявляющий соответствующее свойство. Так как это невозможно (не в С# по крайней мере) для производного типа для добавления новых accessors к переопределенному свойству, объявление свойства этого "оригинального" типа должно содержать все соответствующие аксессоры.

Ответ 2

Вы не перезаписываете метод, вы заменяете определение свойства

Определение свойства по умолчанию включает методы Get/Set, и ваше новое определение включает только метод Get, поэтому имеет смысл, что ваше перезаписанное свойство имеет только Get, а не Set

Edit

Если вы запустите что-то вроде 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 в то время, которые на самом деле являются определениями свойств, а не свойствами в том смысле, о котором вы могли подумать.