首先,这条线
Type enumerableType = typeof(Enumerable);
应该
Type enumerableType = typeof(T);
这是因为MakeGenericMethod
参数预期的实际泛型类型参数,这些参数在Enumerable.Sum<TSource>(this IEnumerable<TSource>
过载情况下是TSource
,即类型可枚举元素的元素。
其次,用于发现聚集泛型方法的标准是不够的,因为比如有很多Sum<TSource>
重载 - 为int
,double
,decimal
等你需要的是找到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;
}
}
我很欣赏表达式树的用法,但是要注意编译它们会有不俗的表现。如果你在生产中使用它,我会看看是否有一个体面的方法来缓存生成的方法。 – willaien
@willaien好点!我做了一个测试,是的,没有缓存,我们似乎失去了所有的好处,因为你指出了大量的编译开销。但是在添加一个缓存之后(这很简单),所有事情都应该是这样,而且这个方法比其他任何一个都快。但是,让我问一些问题 - 你真的需要'Convert.ToDouble'还是仅仅因为反思 - 即属性应该是'双'? –
我不知道他为什么使用Convert.ToDouble,除非他只是随意的想要double而不管实际的类型(int32或double) – willaien