首先,這條線
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