我想创建一个扩展 IQueryable
的方法,用户可以在其中在字符串中指定一个属性名称,他希望通过该名称来区分集合。我想使用带有 HashSet
的逻辑。
我基本上想模拟这段代码:
HashSet<TResult> set = new HashSet<TResult>();
foreach(var item in source)
{
var selectedValue = selector(item);
if (set.Add(selectedValue))
yield return item;
}
使用表达式树。
这是我到目前为止的进展:
private Expression AssembleDistinctBlockExpression (IQueryable queryable, string propertyName)
{
var propInfo = queryable.ElementType.GetProperty(propertyName);
if ( propInfo == null )
throw new ArgumentException();
var loopVar = Expression.Parameter(queryable.ElementType, "");
var selectedValue = Expression.Variable(propInfo.PropertyType, "selectedValue");
var returnListType = typeof(List<>).MakeGenericType(queryable.ElementType);
var returnListVar = Expression.Variable(returnListType, "return");
var returnListAssign = Expression.Assign(returnListVar, Expression.Constant(Activator.CreateInstance(typeof(List<>).MakeGenericType(queryable.ElementType))));
var hashSetType = typeof(HashSet<>).MakeGenericType(propInfo.PropertyType);
var hashSetVar = Expression.Variable(hashSetType, "set");
var hashSetAssign = Expression.Assign(hashSetVar, Expression.Constant(Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(propInfo.PropertyType))));
var enumeratorVar = Expression.Variable(typeof(IEnumerator<>).MakeGenericType(queryable.ElementType), "enumerator");
var getEnumeratorCall = Expression.Call(queryable.Expression, queryable.GetType().GetTypeInfo().GetDeclaredMethod("GetEnumerator"));
var enumeratorAssign = Expression.Assign(enumeratorVar, getEnumeratorCall);
var moveNextCall = Expression.Call(enumeratorVar, typeof(IEnumerator).GetMethod("MoveNext"));
var breakLabel = Expression.Label("loopBreak");
var loopBlock = Expression.Block(
new [] { enumeratorVar, hashSetVar, returnListVar },
enumeratorAssign,
returnListAssign,
hashSetAssign,
Expression.TryFinally(
Expression.Block(
Expression.Loop(
Expression.IfThenElse(
Expression.Equal(moveNextCall, Expression.Constant(true)),
Expression.Block(
new[] { loopVar },
Expression.Assign(loopVar, Expression.Property(enumeratorVar, "Current")),
Expression.Assign(selectedValue, Expression.MakeMemberAccess(loopVar, propInfo)),
Expression.IfThen(
Expression.Call(typeof(HashSet<>), "Add", new Type[] { propInfo.PropertyType }, hashSetVar, selectedValue),
Expression.Call(typeof(List<>), "Add", new Type[] { queryable.ElementType }, returnListVar, loopVar)
)
),
Expression.Break(breakLabel)
),
breakLabel
),
Expression.Return(breakLabel, returnListVar)
),
Expression.Block(
Expression.Call(enumeratorVar, typeof(IDisposable).GetMethod("Dispose"))
)
)
);
return loopBlock;
}
当为变量 loopBlock
调用 Expression.Block
时出现异常,如下所示:
No method 'Add' exists on type 'System.Collections.Generic.HashSet`1[T]'.
最佳答案
Expression.Call
method overload您正在使用的是静态方法。
引用上面的引用资料:
Creates a MethodCallExpression that represents a call to a static (Shared in Visual Basic) method by calling the appropriate factory method.
你需要做的是使用an overload of that method that is for calling instance methods .
代码的相关部分如下所示:
Expression.IfThen(
Expression.Call(hashSetVar, "Add", new Type[] { }, selectedValue),
Expression.Call(returnListVar, "Add", new Type[] { }, loopVar))
注意我们现在如何传递我们需要在 Expression.Call
的第一个参数中调用的实例(表达式) .
另请注意,我们传递了一个空类型参数列表。原因是 Add
此类中的方法没有任何类型参数。类型参数 T
在HashSet<T>
和 List<T>
是在类级别定义的,而不是在方法级别定义的。
只有当它们像这样在方法本身上定义时,您才需要指定类型参数:
void SomeMethod<T1>(...
关于c# - 使用表达式树创建 DistinctBy,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37504370/