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 PropertyInfo
s if I don't want to care about this difference?
That depends on what you mean by "compare both PropertyInfo
s". 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?