13

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!

naacal
  • 620
  • 1
  • 6
  • 16
  • 3
    first you miss "virtual" on the class A oroperty. Then you have overridden a property, which now does not have a setter anymore in B. I find it very intuitive that the binding flags do not allow setting on a readonly property. – Marino Šimić Nov 15 '11 at 17:26
  • @Marino: I think the OP's point is that you can legally do `var b = new B(); b.TestString = "foo";` but reflection will tell you that `B.TestString` has no setter. In this respect `GetProperty` appears to behave differently to `GetMethod` etc. – LukeH Nov 15 '11 at 17:38
  • 2
    @LukeH I am unable to do `var b = new B(); b.TestString = "foo";`. It tells me (correctly) that property TestString cannot be assigned because it is read only. I **can** do `A b = new B(); b.TestString = "foo";`, but that is because I am storing B as A, and A can set the `TestString` property. It doesn't actually do anything though because when getting the value, it reads `B.get_TestString()`, not `A.get_TestString()` – Rachel Nov 15 '11 at 17:48
  • @LukeH I copied/pasted the class definitions in the original question, except I added `virtual` to the `TestString` property definition since it wouldn't compile otherwise. And yes, I'm sure it won't compile :) I'm using .Net 4.0, VS 2010, and Windows 7 if that matters. – Rachel Nov 15 '11 at 18:07
  • 1
    Compiles for me in LinqPad. http://nopaste.info/43c3ee3bde_nl.html But as expected it does not compile if I remove the `override`, shadowing instead of overriding the property. – CodesInChaos Nov 15 '11 at 18:16
  • @Marino: the class A property is virtual of course. I just forgot it in this little code example. – naacal Nov 15 '11 at 18:50

2 Answers2

6

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.

Ani
  • 111,048
  • 26
  • 262
  • 307
  • 2
    I do not quite understand the point of this, isn't this just a roundabout way of saying `typeof(A).GetProperty("TestString")` or `typeof(B).BaseType.GetProperty("TestString")`? – H.B. Nov 15 '11 at 17:41
  • @H.B.: I've tried to address your question that with an edit. – Ani Nov 15 '11 at 17:46
  • this does not help since you cannot invoke the propInfo.SetValue("test", b, null); -< it will complain that the wrong type of object is passed. The propinfo expects an object of type A. maybe you can do it with casting but i have not tried that. – Marino Šimić Nov 15 '11 at 18:15
  • Nice approach Ani, but I'm not really looking for a workaround but for an explanation if this behaviour of .NET reflection is really correct or not. Properties are just methods at runtime and should follow the rules of inheritance that apply to methods - and this is what they do, except for reflection. I'm not adding new definitions to that property nor do I remove them, I'm just overriding the GETTER (get_TestString()) and choose not to override the SETTER (set_TestString()). And this is what happens from code, but reflection tells me that this property has no setter anymore. Which is wrong. – naacal Nov 15 '11 at 19:49
3

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.

Rachel
  • 130,264
  • 66
  • 304
  • 490
  • Sorry but your explanation does not make much sense to me. You cannot override definitions but only functionality. If properties translate into pairs of get_* and set_* methods then all rules of inheritance should apply to properties as they do to methods. And this is just what happens EXCEPT when you look at the property using reflection. Then the behaviour is totally unexpected and weird (at least for me). Because from plain and simple code, the Setter is still there and working as expected. – naacal Nov 15 '11 at 19:30
  • thanks for your answer. So either I am completely wrong about the meta-level of reflection or this is just badly implemented... Is reflection telling me about how the particular object is designed at source-code level or about what it looks like "just now" at runtime? Because at runtime, the property called "TestString" has got a getter (overriden) and a setter (inherited). GetProperties() gives me all properties of an object including the inherited ones. Yes, it is not inherited on "source-code" level, but at actual runtime, it is - no matter what the compiler makes of it in the end. – naacal Nov 15 '11 at 20:34
  • 1
    @naacal My best guess is that Reflection takes the first property definition it comes to, and since the property is defined on B with only a `Get` method, it doesn't bother looking at the base class to see what other definitions there are because it sees that class B has something in place that overwrites what class A has. If you remove the `TestString` property from B, you'll see reflection returns the A definition of TestString – Rachel Nov 16 '11 at 13:05