2016-01-22 44 views
0

我正在尝试创建一个使用Linq聚合器函数(如Sum,Average和Count)的方法。我有以下代码:动态使用LINQ聚合器

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

当我运行这段代码,它抛出一个错误在这条线 “返回Convert.ToDouble(generic.Invoke(NULL,parametersArray));”。

错误:

Object of type 'Manager.Business.Tests.Foo[]'cannot be converted to object of type 'System.Collections.Generic.IEnumerable`1[System.Linq.Enumerable]'.

我能做些什么?

回答

2

首先,这条线

Type enumerableType = typeof(Enumerable); 

应该

Type enumerableType = typeof(T); 

这是因为MakeGenericMethod参数预期的实际泛型类型参数,这些参数在Enumerable.Sum<TSource>(this IEnumerable<TSource>过载情况下是TSource,即类型可枚举元素的元素

其次,用于发现聚集泛型方法的标准是不够的,因为比如有很多Sum<TSource>重载 - 为intdoubledecimal等你需要的是找到double过载。

三,功能非常低效。将为列表的每个元素调用selector func(在您的代码中称为expression)。不仅如此,您使用反射来获得价值,但也反映找到财产本身。至少你应该把GetProperty移到外面。

var result = list.AggregateDynamic("FooValue", "Sum"); 

UPDATE:作为

所有这些问题都可以很容易地通过使用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; 
    } 
} 
+1

我很欣赏表达式树的用法,但是要注意编译它们会有不俗的表现。如果你在生产中使用它,我会看看是否有一个体面的方法来缓存生成的方法。 – willaien

+0

@willaien好点!我做了一个测试,是的,没有缓存,我们似乎失去了所有的好处,因为你指出了大量的编译开销。但是在添加一个缓存之后(这很简单),所有事情都应该是这样,而且这个方法比其他任何一个都快。但是,让我问一些问题 - 你真的需要'Convert.ToDouble'还是仅仅因为反思 - 即属性应该是'双'? –

+0

我不知道他为什么使用Convert.ToDouble,除非他只是随意的想要double而不管实际的类型(int32或double) – willaien

3

的问题是在这里:

MethodInfo sumMethod = typeof(Enumerable).GetMethods().First(
     m => m.Name == func 
      && m.IsGenericMethod); 

您从聚合函数的可能不采取重载获得第一Func<T, double>

试试这个:

MethodInfo sumMethod = typeof(Enumerable).GetMethods().First(
     m => m.Name == func 
      && m.IsGenericMethod 
      && m.ReturnType == typeof(double)); 
+0

而且他可能需要更强健地检查方法签名。 – usr

+0

@usr是的,当然,这只是一个想法。 –

3

让我们退后一步并看看问题:(我猜)你想支持编译时已知类型的聚合函数(因此是泛型的),但不知道什么属性或集合f他们会选择他们。

我建议你采取另一种方法查找的功能和简单地使用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"); 
    } 
} 

试图使人们有可能找到所有的聚合函数正确地为他们每个人的使用反思将是一场噩梦。你可以让编译器用这个来解决凌乱的零件。

+0

有趣的思维方式。虽然我发布了自己的答案,但我喜欢你对问题的(不同的)看法! +1 –

+0

这是更好的代码,但不清楚他是否想采取这种方法。 +1虽然。 – usr