Expression for Type members results in different Expressions (MemberExpression, UnaryExpression)

12,465

Solution 1

The reason this happens is that Age is a value type. In order to coerce an expression returning a value type into Func<Person,object> the compiler needs to insert a Convert(expr, typeof(object)), a UnaryExpression.

For strings and other reference types, however, there is no need to box, so a "straight" member expression is returned.

If you would like to get to the MemberExpression inside the UnaryExpression, you can get its operand:

private static MemberExpression GetMemberExpression<T>(
    Expression<Func<T,object>> exp
) {
    var member = exp.Body as MemberExpression;
    var unary = exp.Body as UnaryExpression;
    return member ?? (unary != null ? unary.Operand as MemberExpression : null);
}

Solution 2

Rather than comparing the Member.Name string, I would suggest comparing the PropertyInfo instances directly for equality, in order to avoid false positives when two properties in distinct classes share the same name.

public static bool IsSameProperty<TSourceA, TSourceB, TPropertyA, TPropertyB>(
    Expression<Func<TSourceA, TPropertyA>> expA,
    Expression<Func<TSourceB, TPropertyB>> expB)
{
    MemberExpression memExpA = expA.Body as MemberExpression;
    MemberExpression memExpB = expB.Body as MemberExpression;

    if (memExpA == null || memExpB == null)
        return false;

    PropertyInfo propA = memExpA.Member as PropertyInfo;
    PropertyInfo propB = memExpB.Member as PropertyInfo;

    if (propA == null || propB == null)
        return false;

    return propA.Equals(propB);
}

You can ensure that your lambda expression is compiled as a MemberExpression rather than a UnaryExpression simply by specifying the correct value type (rather than object) as the generic type TResult of your Expression<Func<T, TResult>> expression.

Expression<Func<Person, int>> expression1 = x => x.Age;
Expression<Func<Person, int>> expression2 = x => x.Age;
Expression<Func<Person, string>> expression3 = x => x.Name;

Console.WriteLine(IsSameProperty(expression1, expression2));   // True
Console.WriteLine(IsSameProperty(expression1, expression3));   // False
Share:
12,465

Related videos on Youtube

dknaack
Author by

dknaack

Updated on September 15, 2022

Comments

  • dknaack
    dknaack over 1 year

    Description

    I have a expression to point on a property of my type. But it does not work for every property type. "Does not mean" means it result in different expression types. I thought it will ever result in a MemberExpression but this is not the case.

    For int and Guid it results in a UnaryExpression and for string in a MemberExpression.

    I am a little confused ;)

    Some sample code

    My class

    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    

    Test Code

    Person p = new Person { Age = 16, Name = "John" };
    
    Expression<Func<Person, object>> expression1 = x => x.Age;
    // expression1.Body = UnaryExpression;
    
    Expression<Func<Person, object>> expression2 = x => x.Name;
    // expression2.Body = MemberExpression;
    

    Question

    How can i compare two expressions and check if they are mean the same type and same property ?

    Update, Answer and complete Sample

    Thanks to user dasblinkenlight who brought me on the right track.

    He provided the method

    private static MemberExpression GetMemberExpression<T>(
        Expression<Func<T,object>> exp
    ) {
        var member = expr.Body as MemberExpression;
        var unary = expr.Body as UnaryExpression;
        return member ?? (unary != null ? unary.Operand as MemberExpression : null);
    }
    

    I wrote the following extension method to compare the results of the GetMemberExpression methods and check if GetMemberExpression().Member.Name are the same.

    private static bool IsSameMember<T>(this Expression<Func<T, object>> expr1, Expression<Func<T, object>> expr2)
    {
        var result1 = GetMemberExpression(expr1);
        var result2 = GetMemberExpression(expr2);
    
        if (result1 == null || result2 == null)
           return false;
    
        return result1.Member.Name == result2.Member.Name;
    }
    
  • Russell Troywest
    Russell Troywest over 11 years
    As for a workaround - could you use Expression<Func<Person, TResult>> and pass the type of the property you are returning? That way you wont need to convert it.
  • dknaack
    dknaack over 11 years
    @dasblinkenlight Thanks a lot! Check out my updated answer. Have a nice day!
  • Scott
    Scott almost 8 years
    If you really want to blow minds, you can use C#6's new nullable property accessors to make this a (rather unreadable) one-liner: MemberExpression member = ((exp.Body as UnaryExpression)?.Operand ?? exp.Body) as MemberExpression