Performance difference with MemberInit Expression

2019-09-12 16:11发布

问题:

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

回答1:

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