Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange Effect with Overridden Properties and Reflection

I've come across a strange behaviour in .NET/Reflection and cannot find any solution/explanation for this:

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

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

Since properties are just pairs of functions (get_PropName(), set_PropName()) overriding only the "get" part should leave the "set" part as it is in the base class. And this is just what happens if you try to instanciate class B and assign a value to TestString, it uses the implementation of class A.

But what happens if I look at the instantiated object of class B in reflection is this:

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

And if I try to invoke the setter from reflection with:

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

I'll even get an ArgumentException with the following message:

Property set method not found.

Is this as expected? Because I don't seem to find a combination of BindingFlags for the GetProperty() method that returns me the property with a working get/set pair from reflection.

EDIT: I would expect that behaviour if I'd use BindingFlags.DeclaredOnly on GetProperties() but the default (BindingFlags.Default) takes inherited members into account and the setter of TestString clearly is inherited!

like image 586
naacal Avatar asked Nov 15 '11 17:11

naacal


2 Answers

Here's a workaround:

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 approach should be reasonably robust. You will need to be more careful with this (BindingFlags) if you're looking for non-public accessor(s).

EDIT:

Note that this approach is different from "hardcoding" typeof(A).GetProperty("TestString") or typeof(B).BaseType.GetProperty("TestString") because it finds the actual, original type that declares the property in question. Since it isn't possible (not in C# at least) for a derived type to add new accessors to an overridden property, the property-declaration on this "original" type should contain all the relevant accessors.

like image 185
Ani Avatar answered Nov 17 '22 08:11

Ani


You're not overwritting a method, you're overwritting a property definition

The default definition of the property includes Get/Set methods, and your new definition only includes a Get method, so it makes sense that your overwritten property only has Get available, not Set

Edit

If you run something like Reflector on this, you'll see that

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

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

compiles into something like that looks like

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

When you set the value in code, you are actually calling something like B.base.set_TestValue. When you reflect something, you are trying to find B.set_TestValue, which doesn't exist.

While true that you cannot overwrite a property, you can overwrite a property definition (providing it doesn't conflict with the base property definition). Since your question was originally tagged with WPF, I was thinking of DependencyProperties at the time, which are actually property definitions, and not properties in the sense that you might be thinking of.

like image 3
Rachel Avatar answered Nov 17 '22 09:11

Rachel