C# 动态使用LINQ聚合器
我正在尝试创建一个使用Linq聚合器函数的方法,比如Sum、Average和Count。我有以下代码:C# 动态使用LINQ聚合器,c#,linq,reflection,C#,Linq,Reflection,我正在尝试创建一个使用Linq聚合器函数的方法,比如Sum、Average和Count。我有以下代码: private double AgreggateDynamic<T>(IEnumerable<T> list, string propertyName, string func) { //Already tried this //IEnumerable<T> listEnum = list.ToList();
private double AgreggateDynamic<T>(IEnumerable<T> list, string propertyName, string func)
{
//Already tried this
//IEnumerable<T> listEnum = list.ToList();
Type enumerableType = typeof(Enumerable);
MethodInfo sumMethod = typeof(Enumerable).GetMethods().First(
m => m.Name == func
&& m.IsGenericMethod);
MethodInfo generic = sumMethod.MakeGenericMethod(enumerableType);
Func<T, double> expression = x => Convert.ToDouble(x.GetType().GetProperty(propertyName).GetValue(x, null));
object[] parametersArray = new object[] { list, expression };
return Convert.ToDouble(generic.Invoke(null, parametersArray));
}
AgreggateDynamic(list, "FooValue", "Sum");
private-double-agreegatedynamic(IEnumerable列表、字符串属性名称、字符串函数)
{
//已经试过了
//IEnumerable listEnum=list.ToList();
类型enumerableType=typeof(可枚举);
MethodInfo sumMethod=typeof(可枚举).GetMethods().First(
m=>m.Name==func
&&m.一般方法);
MethodInfo generic=sumMethod.MakeGenericMethod(enumerableType);
Func expression=x=>Convert.ToDouble(x.GetType().GetProperty(propertyName).GetValue(x,null));
object[]parametersArray=新对象[]{list,expression};
返回Convert.ToDouble(generic.Invoke(null,parametersArray));
}
AgReggegateDynamic(列表,“FooValue”、“Sum”);
当我运行这段代码时,它在这一行“return Convert.ToDouble(generic.Invoke(null,parametersArray));”上抛出一个错误
错误:
无法将类型为“Manager.Business.Tests.Foo[]”的对象转换为类型为“System.Collections.Generic.IEnumerable`1[System.Linq.Enumerable]”的对象
我能做什么?问题在于:
MethodInfo sumMethod = typeof(Enumerable).GetMethods().First(
m => m.Name == func
&& m.IsGenericMethod);
首先是聚合函数的重载,它不能接受Func
请尝试以下方法:
MethodInfo sumMethod = typeof(Enumerable).GetMethods().First(
m => m.Name == func
&& m.IsGenericMethod
&& m.ReturnType == typeof(double));
让我们退一步看看问题:(我猜)您希望在编译时已知的类型上支持聚合函数(因此是泛型),但不知道它们将选择什么属性或聚合函数 我建议您采用另一种方法查找函数,只需使用switch语句,如下所示:
private double AggregateDynamic<T>(IEnumerable<T> list, string propertyName, string func)
{
var propertyInfo = typeof(T).GetProperty(propertyName);
Func<T, double> propertyFunction = x => Convert.ToDouble(propertyInfo.GetValue(x, null));
switch (func)
{
case "Sum":
return list.Sum(propertyFunction);
case "Average":
return list.Average(propertyFunction);
case "Count":
return list.Count();
case "Max":
return list.Max(propertyFunction);
default:
throw new ArgumentException("Unknown aggregate function");
}
}
private double AggregateDynamic(IEnumerable list,string propertyName,string func)
{
var propertyInfo=typeof(T).GetProperty(propertyName);
Func propertyFunction=x=>Convert.ToDouble(propertyInfo.GetValue(x,null));
开关(func)
{
案例“金额”:
返回列表.Sum(propertyFunction);
案例“平均值”:
返回列表。平均值(propertyFunction);
案例“计数”:
返回list.Count();
案例“Max”:
返回列表.Max(propertyFunction);
违约:
抛出新ArgumentException(“未知聚合函数”);
}
}
试图使用反射为每个聚合函数正确找到所有聚合函数将是一场噩梦。您可以让编译器使用此命令为您解决混乱的部分。首先,这一行
Type enumerableType = typeof(Enumerable);
应该是
Type enumerableType = typeof(T);
这是因为MakeGenericMethod
参数期望实际的泛型类型参数,在Enumerable.Sum(此IEnumerable
重载为TSource
)的情况下,实际的泛型类型参数是可枚举的元素的类型
其次,用于查找聚合泛型方法的标准是不够的,因为例如有许多Sum
重载-forint
、double
、decimal
等。您需要的是查找double
的重载
第三,该函数效率非常低。将为列表中的每个元素调用选择器
func(在代码中称为表达式
)。不仅可以使用反射获取值,还可以使用反射查找属性本身。至少应该将GetProperty
移到外部
所有这些问题都可以通过使用System.Linq.Expressions
构建整个过程,编译委托并调用它来轻松解决,如下所示
public static class DynamicAggregator
{
public static double AggregateDynamic<T>(this IEnumerable<T> source, string propertyName, string func)
{
return GetFunc<T>(propertyName, func)(source);
}
static Func<T, double> GetFunc<T>(string propertyName, string func)
{
return BuildFunc<T>(propertyName, func);
}
static Func<T, double> BuildFunc<T>(string propertyName, string func)
{
var source = Expression.Parameter(typeof(IEnumerable<T>), "source");
var item = Expression.Parameter(typeof(T), "item");
Expression value = Expression.PropertyOrField(item, propertyName);
if (value.Type != typeof(double)) value = Expression.Convert(value, typeof(double));
var selector = Expression.Lambda<Func<T, double>>(value, item);
var methodCall = Expression.Lambda<Func<IEnumerable<T>, double>>(
Expression.Call(typeof(Enumerable), func, new Type[] { item.Type }, source, selector),
source);
return methodCall.Compile();
}
}
更新:正如评论中正确指出的那样,Expression.Compile
有很大的性能开销,这基本上扼杀了这种方法的好处。但是,添加缓存已编译委托非常容易,然后一切都会正常进行
为此,首先,我通过分离方法构建/编译部分对初始代码进行了轻微重构。然后,通过如下修改类,添加缓存非常简单:
static readonly Dictionary<Tuple<Type, string, string>, Delegate> funcCache = new Dictionary<Tuple<Type, string, string>, Delegate>();
static Func<IEnumerable<T>, double> GetFunc<T>(string propertyName, string func)
{
var cacheKey = Tuple.Create(typeof(T), propertyName, func);
Delegate cachedValue;
lock (funcCache)
{
if (funcCache.TryGetValue(cacheKey, out cachedValue))
return (Func<IEnumerable<T>, double>)cachedValue;
var method = BuildFunc<T>(propertyName, func);
funcCache.Add(cacheKey, method);
return method;
}
}
static readonly Dictionary funcCache=new Dictionary();
静态Func GetFunc(字符串属性名称,字符串Func)
{
var cacheKey=Tuple.Create(typeof(T),propertyName,func);
委托缓存值;
锁(缓存)
{
if(funcCache.TryGetValue(cacheKey,out cachedValue))
返回(Func)cachedValue;
var方法=BuildFunc(propertyName,func);
添加(cacheKey,方法);
返回法;
}
}
有趣的思维方式。虽然我已经发布了自己的答案,但我喜欢你的(与众不同)问题的观点!+1这是更好的代码,但不清楚他是否愿意采用这种方法。+1尽管如此。我欣赏表达式树的使用,但想指出的是,编译它们的性能会受到相当大的影响。如果你在生产中使用它,我会看看是否有一种合适的方法来缓存生成的方法。@willaien说得好!我同意e做了一个测试,是的,没有缓存我们似乎失去了所有的好处,因为正如您所指出的,编译开销很大一切都是它应该的样子,并且该方法比其他方法快几倍。但是让我先问你一个问题-你真的需要转换。ToDouble还是仅仅因为反射-即属性应该是double
?。我不知道他为什么要使用Convert.ToDouble,除非他只是随意想要double不管实际类型(int32或double)@willaien Oops,我的错,我为这个问题道歉,不知什么原因我认为你是OP:)谢谢!这是最完整的答案,所以我认为它是正确的。这是我第一次尝试使用反射的代码,我发现一些概念是错误的,所以我调用了“returnint”并将其转换为double。但我用的是开关箱,因为我
static readonly Dictionary<Tuple<Type, string, string>, Delegate> funcCache = new Dictionary<Tuple<Type, string, string>, Delegate>();
static Func<IEnumerable<T>, double> GetFunc<T>(string propertyName, string func)
{
var cacheKey = Tuple.Create(typeof(T), propertyName, func);
Delegate cachedValue;
lock (funcCache)
{
if (funcCache.TryGetValue(cacheKey, out cachedValue))
return (Func<IEnumerable<T>, double>)cachedValue;
var method = BuildFunc<T>(propertyName, func);
funcCache.Add(cacheKey, method);
return method;
}
}