簡體   English   中英

為什么創建和使用表達式比直接訪問更快?

[英]Why is creating and using an Expression faster than direct access?

我目前正在實施一些動態過濾/排序,並認為做一個基准測試來了解情況是個好主意。

首先,這里是創建一個充當“吸氣劑”的表達式的方法:

public static Expression<Func<TEntity, object>> GetPropertyGetter(string propertyName, bool useCache = false)
{
    if (useCache && _propertyGetters.ContainsKey(propertyName))
        return _propertyGetters[propertyName];

    var entityType = typeof(TEntity);
    var property = entityType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
    if (property == null)
        throw new Exception($"Property {propertyName} was not found in entity {entityType.Name}");

    var param = Expression.Parameter(typeof(TEntity));
    var prop = Expression.Property(param, propertyName);
    var convertedProp = Expression.Convert(prop, typeof(object));
    var expr = Expression.Lambda<Func<TEntity, object>>(convertedProp, param);

    if (useCache)
    {
        _propertyGetters.Add(propertyName, expr);
    }

    return expr;
}

這是基准:

public class OrderBy
{

    private readonly List<Entry> _entries;

    public OrderBy()
    {
        _entries = new List<Entry>();
        for (int i = 0; i < 1_000_000; i++)
        {
            _entries.Add(new Entry($"Title {i}", i));
        }
    }

    [Benchmark(Baseline = true)]
    public List<Entry> SearchTitle()
    {
        return _entries.AsQueryable().OrderByDescending(p => p.Title).ToList();
    }

    [Benchmark]
    public List<Entry> SearchTitleDynamicallyWithoutCache()
    {
        var expr = DynamicExpressions<Entry>.GetPropertyGetter("Title");
        return _entries.AsQueryable().OrderByDescending(expr).ToList();
    }

    [Benchmark]
    public List<Entry> SearchTitleDynamicallyWithCache()
    {
        var expr = DynamicExpressions<Entry>.GetPropertyGetter("Title", useCache: true);
        return _entries.AsQueryable().OrderByDescending(expr).ToList();
    }

}

public class Entry
{

    public string Title { get; set; }
    public int Number { get; set; }

    public Entry(string title, int number)
    {
        Title = title;
        Number = number;
    }

}

結果如下:
在此處輸入圖像描述

所以我的問題是,為什么創建一個表達式(使用反射來獲取屬性)比直接訪問( p => p.Title )更快?

問題是您的GetPropertyGetter方法生成一個 lambda ,它將屬性的結果轉換為object OrderByobject而不是按string排序時,使用的比較是不同的。 如果將 lambda 更改為p => (object)p.Title ,您會發現它也更快。 如果您將OrderByDescending更改為采用StringComparer.InvariantCulture ,您會發現生成的 lambda 表達式的速度略有加快。

當然,這也意味着您的動態OrderBy很可能無法正確處理其他語言。

不幸的是,一旦您開始為 LINQ 方法動態創建類似 lambda 的代碼,您不能總是只替換object並期望得到相同的結果(例如,一個int字段將被裝箱, string將不會使用相同的比較器,自定義類型比較器可能不起作用,...)。 基本上我認為用於動態類型處理的Expression構建就像 GPL - 它像病毒一樣傳播(和傳播)。 如果您將OrderByDescending(GetPropertyGetter)替換為動態OrderByPropertyNameDescending(string)並構建對OrderBy的調用,您將得到您期望的結果。

考慮:

public static class DynanmicExt {
    public static IOrderedQueryable<TEntity> OrderByDescending<TEntity>(this IQueryable<TEntity> q, string propertyName) {
        var entityType = typeof(TEntity);
        var property = entityType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
        if (property == null)
            throw new Exception($"Property {propertyName} was not found in entity {entityType.Name}");

        var param = Expression.Parameter(typeof(TEntity));
        var prop = Expression.Property(param, propertyName);
        var expr = Expression.Lambda<Func<TEntity,string>>(prop, param);

        var OrderBymi = typeof(Queryable).GetGenericMethod("OrderByDescending", new[] { typeof(IQueryable<TEntity>), typeof(Expression<Func<TEntity, object>>) })
                                         .MakeGenericMethod(typeof(TEntity), prop.Member.GetMemberType());
        var obParam = Expression.Parameter(typeof(IQueryable<TEntity>));
        var obBody = Expression.Call(null, OrderBymi, obParam, expr);
        var obLambda = Expression.Lambda<Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>>(obBody, obParam).Compile();

        return obLambda(q);
    }
}

哦,差點忘了它需要這些方便的反射助手:

public static class MemberInfoExt {
    public static Type GetMemberType(this MemberInfo member) {
        switch (member) {
            case FieldInfo mfi:
                return mfi.FieldType;
            case PropertyInfo mpi:
                return mpi.PropertyType;
            case EventInfo mei:
                return mei.EventHandlerType;
            default:
                throw new ArgumentException("MemberInfo must be if type FieldInfo, PropertyInfo or EventInfo", nameof(member));
        }
    }
}

public static class TypeExt {
    public static MethodInfo GetGenericMethod(this Type t, string methodName, params Type[] pt) =>
        t.GetMethods().Where(mi => mi.Name == methodName && mi.IsGenericMethod && mi.GetParameters().Select(mip => mip.ParameterType.IfGetGenericTypeDefinition()).SequenceEqual(pt.Select(p => p.IfGetGenericTypeDefinition()))).Single();
    public static Type IfGetGenericTypeDefinition(this Type aType) => aType.IsGenericType ? aType.GetGenericTypeDefinition() : aType;
}

現在您可以將它用於:

public List<Entry> SearchTitle2() =>
    _entries.AsQueryable().OrderByDescending("Title").ToList();

現在這與 lambda 的運行速度一樣慢。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM