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
(调试视图路径)中缓存的。
答案 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
来自的地方。 $var1
是param
引用。
下次考虑使用Expression.Parameter(Type, string)
重载,这样可以为调试目的命名参数。它更容易推理。