Выражение для членов типа приводит к разным выражениям (MemberExpression, UnaryExpression)

Описание

У меня есть выражение, указывающее на свойство моего типа. Но он не работает для каждого типа свойства. "Не означает" означает это приводит к разным типам выражений. Я думал, что это когда-нибудь приведет к MemberExpression, но это не так.

Для int и Guid это приводит к a UnaryExpression и для string в MemberExpression.

Я немного смущен;)

Пример кода

Мой класс

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

Тестовый код

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;

Вопрос

Как я могу сравнить два выражения и проверить, являются ли они средними того же типа и того же свойства?

Обновить, ответить и выполнить образец

Благодаря пользователю dasblinkenlight, который привел меня на правильный путь.

Он предоставил метод

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);
}

Я написал следующий метод расширения для сравнения результатов GetMemberExpression методы и проверьте, совпадают ли теги GetMemberExpression().Member.Name.

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;
}

Ответы

Ответ 1

Причина этого в том, что Age - тип значения. Чтобы принудить выражение, возвращающее тип значения в Func<Person,object>, компилятору необходимо вставить Convert(expr, typeof(object)), UnaryExpression.

Для string и других ссылочных типов, однако, нет необходимости вставлять в квадрат, поэтому возвращается выражение "прямое" членство.

Если вы хотите попасть в MemberExpression внутри UnaryExpression, вы можете получить его операнд:

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);
}

Ответ 2

Вместо сравнения строки Member.Name я бы предложил сравнить экземпляры PropertyInfo непосредственно для равенства, чтобы избежать ложных срабатываний, когда два свойства в разных классах имеют одно и то же имя.

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);
}

Вы можете убедиться, что ваше лямбда-выражение скомпилировано как MemberExpression, а не UnaryExpression, просто указав правильный тип значения (а не object) как общий тип TResult вашего выражения Expression<Func<T, TResult>>.

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