Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/266.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/linq/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 动态使用LINQ聚合器_C#_Linq_Reflection - Fatal编程技术网

C# 动态使用LINQ聚合器

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

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

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
重载-for
int
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;
    }
}