如何使用反射在 IEnumerable<T> 上调用 System.Linq.Enumerable.Count<>?

9

我有一些IEnumerable集合,其确切数量和类型会经常发生变化(由于自动生成的代码)。

它看起来像这样:

public class MyCollections {
    public System.Collections.Generic.IEnumerable<SomeType> SomeTypeCollection;
    public System.Collections.Generic.IEnumerable<OtherType> OtherTypeCollection;
    ...

在运行时,我希望能够确定每种类型及其数量,而不必在每次代码生成后重新编写代码。因此,我正在寻找一种使用反射的通用方法。我要得到的结果类似于:

MyType: 23
OtherType: 42

我的问题是我无法正确调用Count方法。这是我目前的代码:

        // Handle to the Count method of System.Linq.Enumerable
        MethodInfo countMethodInfo = typeof(System.Linq.Enumerable).GetMethod("Count", new Type[] { typeof(IEnumerable<>) });

        PropertyInfo[] properties = typeof(MyCollections).GetProperties();
        foreach (PropertyInfo property in properties)
        {
            Type propertyType = property.PropertyType;
            if (propertyType.IsGenericType)
            {
                Type genericType = propertyType.GetGenericTypeDefinition();
                if (genericType == typeof(IEnumerable<>))
                {
                    // access the collection property
                    object collection = property.GetValue(someInstanceOfMyCollections, null);

                    // access the type of the generic collection
                    Type genericArgument = propertyType.GetGenericArguments()[0];

                    // make a generic method call for System.Linq.Enumerable.Count<> for the type of this collection
                    MethodInfo localCountMethodInfo = countMethodInfo.MakeGenericMethod(genericArgument);

                    // invoke Count method (this fails)
                    object count = localCountMethodInfo.Invoke(collection, null);

                    System.Diagnostics.Debug.WriteLine("{0}: {1}", genericArgument.Name, count);
                }
            }
        }

“MyCollections”是变量/字段/属性的一种类型吗?你似乎同时将其用作两者。 - Lasse V. Karlsen
你的 count MethodInfo-reference 是 null。修复它,代码可能会正常工作。 - Lasse V. Karlsen
抱歉,给出的示例不准确。我已经修复了它。 - mbi
我多么希望.NET中有通用的通配符... - Kent Boogaart
在我看答案之前,有人能向我解释一下这个问题吗? - Colonel Panic
4个回答

6

如果您坚持用困难的方式 ;p

更改:

  • 如何获取泛型方法的countMethodInfo
  • Invoke的参数

代码(请注意, obj 是我实例化的 MyCollections ):

    MethodInfo countMethodInfo = typeof (System.Linq.Enumerable).GetMethods().Single(
        method => method.Name == "Count" && method.IsStatic && method.GetParameters().Length == 1);

    PropertyInfo[] properties = typeof(MyCollections).GetProperties();
    foreach (PropertyInfo property in properties)
    {
        Type propertyType = property.PropertyType;
        if (propertyType.IsGenericType)
        {
            Type genericType = propertyType.GetGenericTypeDefinition();
            if (genericType == typeof(IEnumerable<>))
            {
                // access the collection property
                object collection = property.GetValue(obj, null);

                // access the type of the generic collection
                Type genericArgument = propertyType.GetGenericArguments()[0];

                // make a generic method call for System.Linq.Enumerable.Count<> for the type of this collection
                MethodInfo localCountMethodInfo = countMethodInfo.MakeGenericMethod(genericArgument);

                // invoke Count method (this fails)
                object count = localCountMethodInfo.Invoke(null, new object[] {collection});

                System.Diagnostics.Debug.WriteLine("{0}: {1}", genericArgument.Name, count);
            }
        }
    }

非常感谢!在检查了您的回复后,我突然明白了我的错误。实际上,只需要调整Invoke的参数(当然!),然后我的原始实现就像预期的那样工作了。 虽然您使用Linq检索countMethodInfo的方法看起来很性感,但对我来说有点太复杂了;-) - mbi

3

这将涉及到一些MakeGenericMethod,以及大量的反射通常。个人而言,在这种情况下,我会倾向于通过放弃泛型来简化:

public static int Count(IEnumerable data) {
    ICollection list = data as ICollection;
    if(list != null) return list.Count;
    int count = 0;
    IEnumerator iter = data.GetEnumerator();
    using(iter as IDisposable) {
        while(iter.MoveNext()) count++;
    }
    return count;
}

即使通过反射获取,您也可以轻松地将其转换为非泛型的 IEnumerable


@AakashM,Marc,这种方法避免了对Current的不必要调用,这可能(根据枚举器)是昂贵的,并且可能无法被优化掉。@Marc,我会将第一个测试作为ICollection的测试,而不是IList的测试,因为ICollection是定义IList的Count属性的地方,这样你就可以捕获其他集合类型,其中内置了(可能更有效)的Count。 - Jon Hanna
@AakashM - 可能是这样,但简单来说:我们不需要那些数据。当然,使用foreach会更简单。 - Marc Gravell
@leppie。是的,但谁能说这个人(可能不是你)没做错!如果他们从另一个源进行转换,则可以在Current而不是MoveNext上执行此操作。这可能不会非常昂贵,但可能并不简单,并且不是世界上最糟糕的方法(虽然不是我会选择的方法,但无所谓)。 - Jon Hanna
@Jon Hanna:MoveNext 通常会设置当前值,因为它必须返回一个布尔值来指定是否找到了该值。 - leppie
@leppie。是的,通常情况下它确实可以,尽管它可能不会超出在 DataReader 上调用 Read() 的范围。 - Jon Hanna
显示剩余4条评论

2

现在,这个问题已经得到了回答,但我想向您展示一个简化版本(我认为相当琐碎)的“调用通用扩展方法”,它可以用于反射地调用 Count

// get Enumerable (which holds the extension methods)
Type enumerableT = typeof(Enumerable);

// get the Count-method (there are only two, you can check the parameter-count as in above 
// to be certain. Here we know it's the first, so I use the first:
MemberInfo member = enumerableT.GetMember("Count")[0];

// create the generic method (instead of int, replace with typeof(yourtype) in your code)
MethodInfo method = ((MethodInfo) member).MakeGenericMethod(typeof(int));

// invoke now becomes trivial
int count = (int)method.Invoke(null, new object[] { yourcollection });

上述代码有效,因为您不需要使用通用类型IEnumerable<>来调用Count,它是Enumerable的扩展方法,并将IEnumerable<T>作为第一个参数(它是一个扩展方法),但您不需要指定它。
请注意,从您的问题描述中可以看出,您应该实际上使用泛型类型,这样可以为您的项目添加类型安全性,仍然允许您使用Count或其他方法。毕竟,唯一确定的是所有都是Enumerable,对吗?如果是这样,谁还需要反射呢?

反射的原因很简单:你所标记为“yourtype”的内容在编译时是未知的(或者至少经常会变化)。 - mbi
@embee:我理解你的意思。但如果它经常变化(是生成的),那么使用泛型是最理想的。但说实话,我不了解你的情况是否适合这种方法。 - Abel
可能我对泛型的理解还不够,无法理解你的建议,抱歉。在这种情况下,MyCollections是一个必须被接受的事实。 - mbi
@embee:我添加了一条注释,希望它能澄清一些事情。我的理解是,您目前正在执行比必要更多的操作,以使用在编译时不知道类型的类型集合调用静态方法。但也许我完全误解了? :) - Abel
重新阅读后,我发现我跟已经说过的内容非常接近。我被你的foreach循环分心了。抱歉。因此,上面应该这样写:这就是调用通用方法“Count”的全部内容。你的循环是你基本逻辑的一部分。 - Abel

1
var count = System.Linq.Enumerable.Count(theCollection);

编辑:你说它是生成的,那么你不能只生成一个带有对 Count() 的调用的属性吗?

public class MyCollections
{
    public System.Collections.Generic.IEnumerable<SomeType> SomeTypeCollection;
    public System.Collections.Generic.IEnumerable<OtherType> OtherTypeCollection;

    public int CountSomeTypeCollection
    {
        get { return this.SomeTypeCollection.Count(); }
    }

    ...

抱歉,我错过了一个事实,就是你在编译时不知道 T - Kent Boogaart
MyCollections是自动生成的,并且经常被覆盖。这就是为什么我必须从外部使用反射。我知道这很棘手,但应该是可能的...... - mbi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接