与 MemberInit 表达式的性能差异

发布于 2024-07-26 12:07:41 字数 2868 浏览 2 评论 0原文

我正在处理与 问题 222511 类似的问题,我确实需要使用MemberInit 表达式,这样我就可以将它们添加到构造函数中...我正在尝试实现 John Skeet 的 答案,但是我遇到了很大的性能差异。 这是一些代码:

// Method A:
// This work good, is fast and returns an un-executed query...
DataContext.LoanNote.Join<LoanNote, Customer, int, LoanNote>(
    DataContext.Customers, loanNote => loanNote.PrimaryCustomerNumber, customer => customer.CustomerNumber,
(LoanNote loanNote, Customer customer) => new LoanNote()
{
    AccountFeeBillAmount = loanNote.AccountFeeBillAmount,
    AccountOpenDate = loanNote.AccountOpenDate,
    // This goes on and on...
    PrimaryCustomer = customer
});

// Method B:
// This on the other hand is a lot slower and I am not sure why...
var resultSelector = BuildJoinResultSelector<LoanNote, Customer, LoanNote("PrimaryCustomer").Compile();

DataContext.LoanNote.Join<LoanNote, Customer, int, LoanNote>(
        DataContext.Customers, loanNote => loanNote.PrimaryCustomerNumber, customer => customer.CustomerNumber, resultSelector);


// The build MemberInitExpression method...
private static Expression<Func<TOuter, TInner, TResult>> BuildJoinResultSelector<TOuter, TInner, TResult>(string propertyName) where TResult : class
        {
            var result = default(Expression<Func<TOuter, TInner, TResult>>);
            var resultType = typeof(TResult);
            var outerType = typeof(TOuter);
            var innerType = typeof(TInner);
            var outer = Expression.Parameter(outerType, "outer");
            var inner = Expression.Parameter(innerType, "inner");
            var bindings = new List<MemberBinding>();

            foreach (var property in resultType.GetProperties())
            {
                if (property.CanRead == false)
                {
                    continue;
                }
                else if (property.CanWrite == false)
                {
                    continue;
                }
                else if (property.Name == propertyName)
                {
                    var condition = Expression.Condition(Expression.Equal(inner, Expression.Constant(null)), Expression.New(innerType), inner);

                    bindings.Add(Expression.Bind(property, condition));
                }
                else
                {
                    bindings.Add(Expression.Bind(property, Expression.Property(outer, property)));
                }
            }

            var memberInit = Expression.MemberInit(Expression.New(resultType), bindings);

            result = Expression.Lambda<Func<TOuter, TInner, TResult>>(memberInit, outer, inner);

            return result;
        }

I am working a similar problem as Question 222511 I do need to use the MemberInit Expression so I can just add them to the constructor... I am trying to implement John Skeet's answer but I am running into a big performance difference. Here is some of the code:

// Method A:
// This work good, is fast and returns an un-executed query...
DataContext.LoanNote.Join<LoanNote, Customer, int, LoanNote>(
    DataContext.Customers, loanNote => loanNote.PrimaryCustomerNumber, customer => customer.CustomerNumber,
(LoanNote loanNote, Customer customer) => new LoanNote()
{
    AccountFeeBillAmount = loanNote.AccountFeeBillAmount,
    AccountOpenDate = loanNote.AccountOpenDate,
    // This goes on and on...
    PrimaryCustomer = customer
});

// Method B:
// This on the other hand is a lot slower and I am not sure why...
var resultSelector = BuildJoinResultSelector<LoanNote, Customer, LoanNote("PrimaryCustomer").Compile();

DataContext.LoanNote.Join<LoanNote, Customer, int, LoanNote>(
        DataContext.Customers, loanNote => loanNote.PrimaryCustomerNumber, customer => customer.CustomerNumber, resultSelector);


// The build MemberInitExpression method...
private static Expression<Func<TOuter, TInner, TResult>> BuildJoinResultSelector<TOuter, TInner, TResult>(string propertyName) where TResult : class
        {
            var result = default(Expression<Func<TOuter, TInner, TResult>>);
            var resultType = typeof(TResult);
            var outerType = typeof(TOuter);
            var innerType = typeof(TInner);
            var outer = Expression.Parameter(outerType, "outer");
            var inner = Expression.Parameter(innerType, "inner");
            var bindings = new List<MemberBinding>();

            foreach (var property in resultType.GetProperties())
            {
                if (property.CanRead == false)
                {
                    continue;
                }
                else if (property.CanWrite == false)
                {
                    continue;
                }
                else if (property.Name == propertyName)
                {
                    var condition = Expression.Condition(Expression.Equal(inner, Expression.Constant(null)), Expression.New(innerType), inner);

                    bindings.Add(Expression.Bind(property, condition));
                }
                else
                {
                    bindings.Add(Expression.Bind(property, Expression.Property(outer, property)));
                }
            }

            var memberInit = Expression.MemberInit(Expression.New(resultType), bindings);

            result = Expression.Lambda<Func<TOuter, TInner, TResult>>(memberInit, outer, inner);

            return result;
        }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

迷鸟归林 2024-08-02 12:07:41

第二种方法的执行速度会较慢,因为它使用反射(GetProperties 调用)。

如果您多次调用它,您可以像这样缓存 GetProperties 的结果:

static class PropertiesCache<T> { 
    public static readonly PropertyInfo[] Properties = typeof(T).GetProperties();
}

这将针对每种类型仅调用一次 GetProperties; 像这样使用:

foreach (var property in PropertiesCache<TResult>.Properties) {
    if(!property.CanRead || !property.CanWrite) continue;

    //...
}

编辑

您还可以用 LINQ 查询替换整个循环,如下所示:

var memberInit = Expression.MemberInit(Expression.New(typeof(TResult)),
    from property in PropertiesCache<TResult>.Properties
    where property.CanRead && property.CanWrite
    select Expression.Bind(property, property.Name == propertyName ?
        Expression.Coalesce(inner, Expression.New(innerType))
        : Expression.Property(outer, property)
        )
    );

The second method will be slower to execute because it uses reflection (the GetProperties call).

If you are calling it many times, you can cache the result of GetProperties like this:

static class PropertiesCache<T> { 
    public static readonly PropertyInfo[] Properties = typeof(T).GetProperties();
}

This will call GetProperties just once per type; use like this:

foreach (var property in PropertiesCache<TResult>.Properties) {
    if(!property.CanRead || !property.CanWrite) continue;

    //...
}

EDIT:

You can also replace your entire loop with a LINQ query, like this:

var memberInit = Expression.MemberInit(Expression.New(typeof(TResult)),
    from property in PropertiesCache<TResult>.Properties
    where property.CanRead && property.CanWrite
    select Expression.Bind(property, property.Name == propertyName ?
        Expression.Coalesce(inner, Expression.New(innerType))
        : Expression.Property(outer, property)
        )
    );
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文