表达几乎是同意的,但其中一个不起作用

时间:2017-02-09 09:19:05

标签: c# .net linq expression

Expressions并不是真正的,但应该是。它们的细节略有不同。我对Expressions很新,但我认为这对于经验丰富的玩家来说可能会令人困惑。我重构了处理某些数据的代码,以使Expression用作IQueryable.Where()的参数。就我所见,它在功能上是等效的。

我这里有以前的代码,运行良好,并生成功能完善的表达式:

private Expression<Func<T, bool>> StringPropertyContains<T>(string propertyName, string value)
{
    if (string.IsNullOrWhiteSpace(propertyName))
    {
        throw new ArgumentNullException(nameof(propertyName));
    }

    var param = Expression.Parameter(typeof(T));

    MemberExpression member = null;
    if (propertyName.Contains('/'))
    {
        var splittedPropertyName = propertyName.Split('/');

        var propertyInfo = this.GetPropertyInfo(typeof(T), splittedPropertyName.First());
        member = Expression.MakeMemberAccess(param, propertyInfo);

        for (int i = 1; i < splittedPropertyName.Length; i++)
        {
            if (propertyInfo.PropertyType.IsInterface)
            {
                //specifically for IActorWithExtraDetails -> reason to refactor
                if (typeof(IActor).IsAssignableFrom(propertyInfo.PropertyType) && typeof(IActor).GetProperties().FirstOrDefault(pi => pi.Name.Equals(splittedPropertyName[i], StringComparison.OrdinalIgnoreCase)) != null)
                {
                    propertyInfo = this.GetPropertyInfo(typeof(IActor), splittedPropertyName[i]);
                }
                else
                {
                    propertyInfo = this.GetPropertyInfo(propertyInfo.PropertyType, splittedPropertyName[i]);
                }
            }
            else
            {
                propertyInfo = this.GetPropertyInfo(propertyInfo.PropertyType, splittedPropertyName[i]);
            }

        }

        member = Expression.MakeMemberAccess(member, propertyInfo);
    }
    else
    {
        var propertyInfo = this.GetPropertyInfo(typeof(T), propertyName);

        member = Expression.MakeMemberAccess(param, propertyInfo);
    }

    var constant = Expression.Constant(value, typeof(string));
    var methodInfo = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
    var body = Expression.Call(member, methodInfo, constant);

    return Expression.Lambda<Func<T, bool>>(body, param);
}

它在DebugView的{​​{1}}属性中的显示方式:

IQueryable

以下是新的重构代码移动到自己的方法:

.Lambda #Lambda2<System.Func`2[AccessManagement.Model.Application,System.Boolean]>(AccessManagement.Model.Application $var1)
{
    .Call ($var1.Name).Contains("hive")
}

这是重构方法的表达式在private Expression<Func<T, bool>> StringPropertyContains<T>(string propertyName, string value) { if (string.IsNullOrWhiteSpace(propertyName)) { throw new ArgumentNullException(nameof(propertyName)); } var param = Expression.Parameter(typeof(T)); MemberExpression member = this.GetMemberExpression(typeof(T), propertyName.Trim('/').Split('/')); var constant = Expression.Constant(value, typeof(string)); var methodInfo = typeof(string).GetMethod("Contains", new Type[] { typeof(string) }); var body = Expression.Call(member, methodInfo, constant); return Expression.Lambda<Func<T, bool>>(body, param); } private MemberExpression GetMemberExpression(Type baseType, string[] path) { MemberExpression result = null; Type type = baseType; PropertyInfo propertyInfo = null; foreach (string segment in path) { //if type is interface, just spray and pray if (type.IsInterface) { propertyInfo = this.GetDescendantProperties(type) .FirstOrDefault(pi => pi.Name.Equals(segment, StringComparison.OrdinalIgnoreCase)); } else { propertyInfo = this.GetPropertyInfo(type, segment); } if (propertyInfo == null) { throw new ArgumentNullException(nameof(propertyInfo)); } result = result == null ? Expression.MakeMemberAccess(Expression.Parameter(baseType), propertyInfo) : Expression.MakeMemberAccess(result, propertyInfo); } return result; } 中的表达方式:

DebugView

只有一个区别。如您所见,第二种情况为.Lambda #Lambda2<System.Func`2[AccessManagement.Model.Application,System.Boolean]>(AccessManagement.Model.Application $var1) { .Call ($var2.Name).Contains("hive") } ,而不是$var2。该变量不存在于整个表达式树中。我不明白为什么,但我打赌这是问题,因为其他一切都是一样的。唯一不同的是,在第二种情况下,$var1处理是在Expression(调试视图路径)中缓存的。

1 个答案:

答案 0 :(得分:2)

在您的第一个代码段中,您要声明一个参数:

var param = Expression.Parameter(typeof(T));

这用作lambda参数,在此处的代码中使用:

 Expression.MakeMemberAccess(param, propertyInfo);

(实际上这被称为两次)。所以代码使用传递给lambda的参数。

在你的第二个代码段中,你仍然使用param作为lambda的参数,但是你不能在lambda体中的任何地方使用它。

您反而称之为:

Expression.MakeMemberAccess(Expression.Parameter(baseType), propertyInfo)

Expression.Parameter(baseType)创建第二个无关变量,该变量永远不会获得分配给它的实际值。你应该在这里使用param引用。

$var2来自的地方。 $var1param引用。

下次考虑使用Expression.Parameter(Type, string)重载,这样可以为调试目的命名参数。它更容易推理。