从 Expression> 获取字符串形式的属性

2023-12-10

我使用一些强类型表达式进行序列化,以允许我的 UI 代码具有强类型排序和搜索表达式。这些都是类型Expression<Func<TModel,TProperty>>并按如下方式使用:SortOption.Field = (p => p.FirstName);。我已经在这个简单的案例中完美地工作了。

我用来解析“FirstName”属性的代码实际上是重用了我们使用的第三方产品中的一些现有功能,并且效果很好,直到我们开始使用深度嵌套的属性(SortOption.Field = (p => p.Address.State.Abbreviation);)。该代码在需要支持深度嵌套属性方面有一些非常不同的假设。

至于这段代码的作用,我不太理解它,我认为我应该从头开始编写这个功能,而不是更改该代码。然而,我不知道有一个good方法来做到这一点。我怀疑我们可以做一些比执行 ToString() 和执行字符串解析更好的事情。那么,处理琐碎且深度嵌套的情况的好方法是什么?

要求:

  • 给定表达式p => p.FirstName我需要一串"FirstName".
  • 给定表达式p => p.Address.State.Abbreviation我需要一串"Address.State.Abbreviation"

虽然这对于我的问题的答案并不重要,但我怀疑我的序列化/反序列化代码可能对将来发现这个问题的其他人有用,所以它如下。再说一次,这段代码对这个问题并不重要——我只是认为它可能对某人有帮助。注意DynamicExpression.ParseLambda来自动态LINQ东西和Property.PropertyToString()这就是这个问题的目的。

/// <summary>
/// This defines a framework to pass, across serialized tiers, sorting logic to be performed.
/// </summary>
/// <typeparam name="TModel">This is the object type that you are filtering.</typeparam>
/// <typeparam name="TProperty">This is the property on the object that you are filtering.</typeparam>
[Serializable]
public class SortOption<TModel, TProperty> : ISerializable where TModel : class
{
    /// <summary>
    /// Convenience constructor.
    /// </summary>
    /// <param name="property">The property to sort.</param>
    /// <param name="isAscending">Indicates if the sorting should be ascending or descending</param>
    /// <param name="priority">Indicates the sorting priority where 0 is a higher priority than 10.</param>
    public SortOption(Expression<Func<TModel, TProperty>> property, bool isAscending = true, int priority = 0)
    {
        Property = property;
        IsAscending = isAscending;
        Priority = priority;
    }

    /// <summary>
    /// Default Constructor.
    /// </summary>
    public SortOption()
        : this(null)
    {
    }

    /// <summary>
    /// This is the field on the object to filter.
    /// </summary>
    public Expression<Func<TModel, TProperty>> Property { get; set; }

    /// <summary>
    /// This indicates if the sorting should be ascending or descending.
    /// </summary>
    public bool IsAscending { get; set; }

    /// <summary>
    /// This indicates the sorting priority where 0 is a higher priority than 10.
    /// </summary>
    public int Priority { get; set; }

    #region Implementation of ISerializable

    /// <summary>
    /// This is the constructor called when deserializing a SortOption.
    /// </summary>
    protected SortOption(SerializationInfo info, StreamingContext context)
    {
        IsAscending = info.GetBoolean("IsAscending");
        Priority = info.GetInt32("Priority");

        // We just persisted this by the PropertyName. So let's rebuild the Lambda Expression from that.
        Property = DynamicExpression.ParseLambda<TModel, TProperty>(info.GetString("Property"), default(TModel), default(TProperty));
    }

    /// <summary>
    /// Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with the data needed to serialize the target object.
    /// </summary>
    /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> to populate with data. </param>
    /// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext"/>) for this serialization. </param>
    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        // Just stick the property name in there. We'll rebuild the expression based on that on the other end.
        info.AddValue("Property", Property.PropertyToString());
        info.AddValue("IsAscending", IsAscending);
        info.AddValue("Priority", Priority);
    }

    #endregion
}

诀窍是:这种形式的任何表达......

obj => obj.A.B.C // etc.

...实际上只是一堆嵌套MemberExpression对象。

首先你有:

MemberExpression: obj.A.B.C
Expression:       obj.A.B   // MemberExpression
Member:           C

评估Expression above as a MemberExpression给你:

MemberExpression: obj.A.B
Expression:       obj.A     // MemberExpression
Member:           B

最后,上面that(在“顶部”)你有:

MemberExpression: obj.A
Expression:       obj       // note: not a MemberExpression
Member:           A

所以很明显,解决这个问题的方法是检查Expression的财产MemberExpression直到它不再是它自己MemberExpression.


UPDATE: 看来你的问题有一个额外的旋转。您可能有一些 lambdalooks like a Func<T, int>...

p => p.Age

...但是actually a Func<T, object>;在这种情况下,编译器会将上面的表达式转换为:

p => Convert(p.Age)

调整这个问题实际上并不像看起来那么困难。看一下我更新的代码,了解一种处理方法。请注意,通过抽象代码来获取MemberExpression进入它自己的方法(TryFindMemberExpression),这种方法保持了GetFullPropertyName方法相当干净,并允许您在将来添加额外的检查——如果,也许,您发现自己面临着new您最初没有考虑到的场景 - 无需费力地阅读太多代码。


举例来说:这段代码对我有用。

// code adjusted to prevent horizontal overflow
static string GetFullPropertyName<T, TProperty>
(Expression<Func<T, TProperty>> exp)
{
    MemberExpression memberExp;
    if (!TryFindMemberExpression(exp.Body, out memberExp))
        return string.Empty;

    var memberNames = new Stack<string>();
    do
    {
        memberNames.Push(memberExp.Member.Name);
    }
    while (TryFindMemberExpression(memberExp.Expression, out memberExp));

    return string.Join(".", memberNames.ToArray());
}

// code adjusted to prevent horizontal overflow
private static bool TryFindMemberExpression
(Expression exp, out MemberExpression memberExp)
{
    memberExp = exp as MemberExpression;
    if (memberExp != null)
    {
        // heyo! that was easy enough
        return true;
    }

    // if the compiler created an automatic conversion,
    // it'll look something like...
    // obj => Convert(obj.Property) [e.g., int -> object]
    // OR:
    // obj => ConvertChecked(obj.Property) [e.g., int -> long]
    // ...which are the cases checked in IsConversion
    if (IsConversion(exp) && exp is UnaryExpression)
    {
        memberExp = ((UnaryExpression)exp).Operand as MemberExpression;
        if (memberExp != null)
        {
            return true;
        }
    }

    return false;
}

private static bool IsConversion(Expression exp)
{
    return (
        exp.NodeType == ExpressionType.Convert ||
        exp.NodeType == ExpressionType.ConvertChecked
    );
}

Usage:

Expression<Func<Person, string>> simpleExp = p => p.FirstName;
Expression<Func<Person, string>> complexExp = p => p.Address.State.Abbreviation;
Expression<Func<Person, object>> ageExp = p => p.Age;

Console.WriteLine(GetFullPropertyName(simpleExp));
Console.WriteLine(GetFullPropertyName(complexExp));
Console.WriteLine(GetFullPropertyName(ageExp));

Output:

FirstName
Address.State.Abbreviation
Age
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从 Expression> 获取字符串形式的属性 的相关文章

随机推荐