有一种聪明的方法可以动态调用类型相关的扩展方法吗?

时间:2013-12-31 20:47:33

标签: c# linq

下面的代码将编译但在运行时失败。它只是为了让我知道我正在尝试做什么。我想要做的是创建一个接受对象集合的方法(实际上是“无类型”集合),如果此集合由单个类型的数字组成,则使用Average()扩展方法返回平均值

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace nnConsole {
    class Program {
        static void Main(string[] args) {
            var ints = new object[4] { 1, 2, 3, 4 };
            var dbls = new object[4] { 1.0, 2.0, 3.0, 4.0 };
            Console.WriteLine(ReallyMean(ints));    
            Console.WriteLine(ReallyMean(dbls));    
            }

        static public double ReallyMean(ICollection<object> data) {
            var unique = data.Distinct();
            var types = unique.Select(x => x.GetType()).Distinct();
            if (types.Count() == 1) {
                if (types.First().IsValueType) {
                    /***********************
                     * magic here to create ddata var that will
                     * call the appropriate extension method for average */ 
                    dynamic ddata = data; // DOES NOT WORK
                    return ddata.Average();
                    }
                }
            return double.NaN;
            }
        }
    }

我确信我可以使用反射来找到合适的Average方法并调用它(不太漂亮)。是否有更短/更清洁/更好的方法来做到这一点?我可以使用最新的C#和.NET运行时。

3 个答案:

答案 0 :(得分:3)

由于您的代码始终返回double,因此您要查找的是将您的对象转换为double的lambda:

var res = data.Cast<dynamic>().Average(n => (double) n);

请注意,您的程序也将在没有Cast<dynamic>()的情况下进行编译,但除非基础数据类型为double,否则它将在运行时失败。

答案 1 :(得分:2)

调用ddata.Average()将导致动态运行时尝试在ICollection<object>上找到正确的成员方法。它不会转换为静态方法调用,扩展方法实际上是在语法糖之下。

然而,您可以重写它以使用普通语法调用扩展方法。如果同一个类中有许多匹配的扩展方法,那么将允许动态重载解析,但它不允许自动选择来自不同类的重载:

return Enumerable.Average(ddata);

这将绑定到Average的正确重载,具体取决于IEnumerable<T>编译时类型。您现在正在使用IEnumerable<object>,因此我猜您必须将其更改为IEnumerable<dynamic>以使其正确。

答案 2 :(得分:0)

只需获取类型,然后转换为该类型即可访问Average

if( types.First() == typeof(int) ){
 return unique.Cast<int>().Average();
}else{
 if( types.First() == typeof(double) ){
  return unique.Cast<double>().Average();
 }
}