Lambda expression not returning expected MemberInfo
Asked Answered
M

4

24

I'm running into a problem that I did not expect. An example will probably illustrate my question better than a paragraph:

UPDATED: Skip to last code-block for a more eloquent code example.

public class A
{
  public string B { get; set; }
}

public class C : A { }

Here is some code from a method:

var a = typeof(C).GetMember("B")[0];
var b = typeof(A).GetMember("B")[0];

Expression<Func<C, string>> c = x => x.B;

var d = (c.Body as MemberExpression).Member;

Here are the results of some comparisons:

a == b //false
a == d //false
b == d //true

The first two are somewhat unexpected. I understand that even though B is not virtual, C could define a property with the same name with thew new operator, but in this case I did not.

The second is really the most surprising to me (and is the heart of my problem). Even though the parameter for the lambda is clearly defined as being of type C, it still returns it as if the property was accessed from the base class.

What I'm looking for is a way to get the MemberInfo from a lambda expression as if I had used reflection on the type of the parameter to get the MemberInfo. My project essentially stores MemberInfos in a dictionary of sorts and it needs to have functionality where you can access the elements by providing a lambda expression.

Restated code sample by Danny Chen

public class Base
{
    public string Name { get; set; }
}
public class Derived : Base { }

//in Main
var parentMember = typeof(Base).GetMember("Name")[0];
var childMember = typeof(Derived).GetMember("Name")[0];

Expression<Func<Base, string>> parentExp = x => x.Name;
var parentExpMember = (parentExp.Body as MemberExpression).Member;

Expression<Func<Derived, string>> childExp = x => x.Name;
var childExpMember = (childExp.Body as MemberExpression).Member;

parentMember == childMember  //false, good
parentMember == parentExpMember  //true, good
childMember == childExpMember   //false, why?
Morbidezza answered 12/7, 2011 at 1:42 Comment(0)
L
22

Take the type of the expression's (first) parameter, and say

Expression<Func<C, string>> c = x => x.B; 
Type paramType = c.Parameters[0].Type;  // first parameter of expression
var d = paramType.GetMember((c.Body as MemberExpression).Member.Name)[0];
Lillis answered 12/7, 2011 at 2:7 Comment(2)
This is likely what I'll end up doing, but I was hoping for something a little more clean than actually using reflection. If there isn't another answer I like better after a while, I will mark this as correct and use it.Morbidezza
I looked at a couple of other open source projects that have similar functionality, and this is how they seem to get around the issue. Thanks.Morbidezza
I
10

What I'm looking for is a way to get the MemberInfo from a lambda expression as if I had used reflection on the type of the parameter to get the MemberInfo.

That's not a service that expression tree conversions on lambdas were designed to provide. If you're going to use a feature "off label" then you might not get the results that you want.

The purpose of expression trees is to proffer up the compiler's semantic analysis of the expression in a form amenable to analysis at runtime, rather than compile time for the purpose of constructing query objects that can be remoted over to databases.

The compiler's correct semantic analysis is that Name is declared as a property of Base and invoked on an instance of Derived, so that's exactly the information that you get out of the resulting expression tree.

Inn answered 12/7, 2011 at 21:57 Comment(1)
I'm guessing by invoked you are referring to the actual type that the property is being accessed through. In the restated example, childExpMember has the same type for DeclaredType as it does for ReflectedType. MSDN states that ReflectedType "Gets the class object that was used to obtain this instance of MemberInfo." Since the property is accessed through the Derived type, then ReflectedType should point to that since that is how the member is being accessed. I realize that there isn't an actual instance of Derived, it's just an expression, but it does seem weird to me.Morbidezza
A
8

Nice question. I use some other names to make it clearer.

public class Base
{
    public string Name { get; set; }
}
public class Derived : Base { }

//in Main
var parentMember = typeof(Base).GetMember("Name")[0];
var childMember = typeof(Derived).GetMember("Name")[0];

Expression<Func<Base, string>> parentExp = x => x.Name;
var parentExpMember = (parentExp.Body as MemberExpression).Member;

Expression<Func<Derived, string>> childExp = x => x.Name;
var childExpMember = (childExp.Body as MemberExpression).Member;

parentMember == childMember  //false, good
parentMember == parentExpMember  //true, good
childMember == childExpMember   //false, why?

When debugging you will find childExpMember.ReflectedType is Base, while childMember.ReflectedType is Derived. AFAIK DeclaringType shows where the member is declared, while ReflectedType shows where the member is reflected to (because of inheritance/overriding/etc). So I think it's a bug (no official confirmation).

Austral answered 12/7, 2011 at 3:10 Comment(1)
I can buy that parentMember != childMember, though it is a bit surprising since Derived doesn't override the base property in any way. It would be cool, and suck at the same time, if I've found a bug in a part of the framework that is used so much. Where's the best place to submit this for a bug and have someone at Microsoft verify it? Also, I'm going to take your code and update my question, it is easier to read.Morbidezza
S
2

I believe you need to pass the flag "FlattenHierarchy" into the bindingAttr parameter of GetMember.

From MSDN:

Specifies that public and protected static members up the hierarchy should be returned. Private static members in inherited classes are not returned. Static members include fields, methods, events, and properties. Nested types are not returned.

Slather answered 12/7, 2011 at 2:9 Comment(1)
Good idea, but I tried it with the sample code and it produced the same results.Morbidezza

© 2022 - 2024 — McMap. All rights reserved.