1

Extracting PropertyInfo seems to yield inconsistent results depending on the strategy used. Here is an example of the issue I'm running into: let's define this simple class hierarchy.

abstract class Top { public abstract int Count { get; set; } }
class BelowTop : Top { public override int Count { get; set; } }

I try to extract the property Count using reflection:

var info1 = typeof(BelowTop).GetProperty("Count");
Console.WriteLine("{0} -> {1}", info1.DeclaringType.Name, info1);
// BelowTop -> Int32 Count

... which tells me that the type actually declaring Count is the concrete class BelowTop.

If I now try to extract this information from an expression:

Expression<Func<BelowTop, int>> lambda = b => b.Count;
var info2 = ((MemberExpression)lambda.Body).Member;
Console.WriteLine("{0} -> {1}", info2.DeclaringType.Name, info2);
// Top -> Int32 Count

... reflection suddenly disagrees on the DeclaringType of what should be the same property.

If I add yet another layer I keep getting the same results:

class Bottom : BelowTop { }
var info3 = typeof(Bottom).GetProperty("Count");
Console.WriteLine("{0} -> {1}", info3.DeclaringType.Name, info3);
// BelowTop -> Int32 Count
Expression<Func<Bottom, int>> lambda2 = b => b.Count;
var info4 = ((MemberExpression)lambda2.Body).Member;
Console.WriteLine("{0} -> {1}", info4.DeclaringType.Name, info4);
// Top -> Int32 Count

Is that a bug? If not what is the logic behind this ? How can I compare both PropertyInfos if I don't want to care about this difference?

Contactomorph
  • 305
  • 2
  • 10
  • 1
    Possible duplicate of [Lambda expression not returning expected MemberInfo](https://stackoverflow.com/questions/6658669/lambda-expression-not-returning-expected-memberinfo). This is "working as designed" (though opinions vary) in that the access in the lambda really is happening through `Top` (because it calls a virtual method) whereas getting the property directly from `BelowTop` will not walk the hierarchy to see if it's an override -- the property is implemented on `BelowTop`, so that's what you get. – Jeroen Mostert Oct 14 '19 at 20:37

1 Answers1

1

Is that a bug?

No.

If not what is the logic behind this?

For a definitive answer, only the designers of the .NET type introspection features (i.e. "reflection"), and/or the designers of C# can say. However, it's important to recognize here that your two scenarios aren't really the same. In the first, you have explicitly stated which type it is you are inspecting. In the second, you've left that decision up to the compiler and it chose something other than what you apparently expected.

As for why the compiler does it this way, IMHO it's important to understand the difference between those two scenarios. In the first, you are asking the reflection API to tell you something about the specific derived type BelowTop. It's specifically overridden a member of its base class, by declaring a new implementation for that member. Thus, the member you're asking about was in fact declared by BelowTop.

On the other hand, the second scenario involves a virtual dispatch call to the given member. From the compiler's point of view, the only thing that matters is which member in the virtual dispatch table ("v-table") is being called. And from that point of view, the member of interest was declared in the Top class (i.e. that's the class where the v-table entry was actually allocated.

After all, why should the lambda body care about the exact type of object passed in? It doesn't have to be a BelowTop instance. It could instead be an instance derived from that type (e.g. your Bottom type in your third example). The same virtual member will be called regardless, and what matters it the type for which the virtual slot was allocated, so that's the type that's captured by the Expression.

In fact, maybe you can see how misleading it would be for your DeclaringType result to return BelowTop in the Expression case. If you had in fact used the lambda with a Bottom instance and that type had overridden the Count property, the reported "declaring type" would be inaccurate, with respect to the Expression. The only pertinent result is what tells you how that lambda expression will behave, i.e. that it will be calling the virtual member declared in Top.

There's no such confusion when you specify the type exactly, because it's clear you want to know about that exact type, and in any case can always work your way back to glean additional information from base classes if that's really what you want.

How can I compare both PropertyInfos if I don't want to care about this difference?

That depends on what you mean by "compare both PropertyInfos". What kind of comparison do you want to make? For what reason? What outcome do you expect?

Ultimately, it may be difficult to accomplish unless your code's view of the scenario is in alignment with the compiler's view.

For example, while reflection can provide a great wealth of information about the original code that was written, there are limits to this. It cannot, for example, tell you about comments which were included in the code, nor about specific arrangement of whitespace. These things are not of interest to the compiler with respect to generating the IL, and those features are not preserved in the transformation from the source code to IL.

Likewise, the compiler has a very specific job when it's asked to build an Expression from a lambda. That job is to represent using the Expression API in .NET the code that would have been generated if the lamba had actually been compiled. So unless your code's desired view of the lambda is in alignment with this goal, i.e. you are willing to concern yourself only with things that are important with respect to code-generation of the IL, inspecting the Expression that represents the lambda may not allow you to achieve whatever goal you want to achieve. The information may simply not be present at all, in that context.

That said, if your goal can be achieved by simply ensuring that the same virtual member is the one being called, regardless of which class has overridden it, then in the first scenario, i.e. where you are getting the member information from an explicitly-stated Type object, you can work your way back up the inheritance chain to find the actual declaring type, and compare that to the member information you've retrieved from the expression object.

Alternatively, if it's okay to care only about the parameter type of the expression, you can access that from the Expression object instead of the expression's Body.


Finally, I'll note that your example is a little bit extra-complicated, as compared to had you been asking about e.g. a field or method, because of how properties and reflection interact. In particular, because a property is really two separate methods, reflection can return potentially seemingly-conflicting information depending on whether you look at the PropertyInfo or MethodInfo for the members.

For more on that particular aspect, you may find these Q&A helpful to read:
Why does sealed override of a property in C# copy not overriden accessor from base type?
Why do CanRead and CanWrite return false in C# for properties with overridden accessors?

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136