如何在C#中使用反射获取集合元素类型

目录

介绍

概念化这个混乱

编码此混乱


介绍

通过这个技巧,我将努力向读者展示如何使用反射来查询一个集合类的元素类型。当涉及到未实现IEnumerable<T>的类型化集合时,一开始看起来相对简单的事情很快就变得复杂起来了。在.NET 2.0之前创建的类型化集合就是这种情况。这些集合非常普遍。例如,许多Windows窗体以及CodeDOM都是这样的集合的类型。这些集合更需要获取元素类型。

概念化这个混乱

有时我们可能需要通过反射来获取集合的元素类型。我通常在编写代码生成器时遇到此问题。这对于.NET post 1.1而言是微不足道的,但是在此之前,由于无法创建可以处理该类型的通用接口,因此没有用于类型化集合的标准接口。

为了获得通用的集合类型,在这种情况下,我们要做的就是查询IEnumerable<T>接口,然后返回任何T内容。简单易用,它也适用于词典。

不幸的是,为了获得非泛型的集合元素类型,我们必须使用一些启发式方法。

我们要做的第一件事是查询IDictionary接口。如果找到它,我们将返回DictionaryEntry

如果没有结果,接下来我们进行查询IList,如果找到它,我们将寻找一个采用单个整数参数并返回除object以外的其他类型的公共索引器属性。

最后,如果找不到,我们查找ICollection,并查找具有单个参数的Add()方法,该参数不是object类型的。我发现这是确定集合的元素类型的最可靠方法。

最后,如果那行不通,我们寻找IEnumerable并找到它,然后返回object类型。否则,我们返回null表明它不是集合类型。我们可以在枚举器的IEnumerator接口上查询Current属性,但是,没有可靠的方法可以在不调用实例上的GetEnumerator()的情况下获得枚举数的类型。在实践中,我认为我没有看到太多不是泛型的类型化枚举器实现。

编码此混乱

像我通常所做的那样,我将几乎完整地发布代码,然后从上至下进行处理:

static partial class ReflectionUtility
{
    /// <summary>
    /// Indicates whether or not the specified type is a list.
    /// </summary>
    /// <param name="type">The type to query</param>
    /// <returns>True if the type is a list, otherwise false</returns>
    public static bool IsList(Type type)
    {
        if (null == type)
            throw new ArgumentNullException("type");

        if (typeof(System.Collections.IList).IsAssignableFrom(type))
            return true;
        foreach (var it in type.GetInterfaces())
            if (it.IsGenericType && typeof(IList<>) == it.GetGenericTypeDefinition())
                return true;
        return false;
    }
    /// <summary>
    /// Retrieves the collection element type from this type
    /// </summary>
    /// <param name="type">The type to query</param>
    /// <returns>The element type of the collection or null if the type was not a collection
    /// </returns>
    public static Type GetCollectionElementType(Type type)
    {
        if (null == type)
            throw new ArgumentNullException("type");

        // first try the generic way
        // this is easy, just query the IEnumerable<T> interface for its generic parameter
        var etype = typeof(IEnumerable<>);
        foreach (var bt in type.GetInterfaces())
            if (bt.IsGenericType && bt.GetGenericTypeDefinition() == etype)
                return bt.GetGenericArguments()[0];
            
        // now try the non-generic way

        // if it's a dictionary we always return DictionaryEntry
        if (typeof(System.Collections.IDictionary).IsAssignableFrom(type))
            return typeof(System.Collections.DictionaryEntry);
            
        // if it's a list we look for an Item property with an int index parameter
        // where the property type is anything but object
        if (typeof(System.Collections.IList).IsAssignableFrom(type))
        {
            foreach (var prop in type.GetProperties())
            {
                if ("Item" == prop.Name && typeof(object)!=prop.PropertyType)
                {
                    var ipa = prop.GetIndexParameters();
                    if (1 == ipa.Length && typeof(int) == ipa[0].ParameterType)
                    {
                        return prop.PropertyType;
                    }
                }
            }
        }

        // if it's a collection, we look for an Add() method whose parameter is 
        // anything but object
        if(typeof(System.Collections.ICollection).IsAssignableFrom(type))
        {
            foreach(var meth in type.GetMethods())
            {
                if("Add"==meth.Name)
                {
                    var pa = meth.GetParameters();
                    if (1 == pa.Length && typeof(object) != pa[0].ParameterType)
                        return pa[0].ParameterType;
                }
            }
        }
        if (typeof(System.Collections.IEnumerable).IsAssignableFrom(type))
            return typeof(object);
        return null;
    }
}

首先,我们有一个上面没有提到的IsList()方法。这是一种实用程序方法,当我对集合进行反射时,我发现自己需要很多,因此在此提供了它。它所做的只是确定传入的类型是否为列表。

现在,在GetCollectionElementType()中,我们将按照本文概念部分中概述的步骤进行操作。首先,我们尝试确定元素类型的泛型方法,如果不成功,我们将转到非泛型测试部分,在该部分中,我们首先查找this[]索引器属性,如果失败,则使用该Add()方法。

使用代码非常简单:

Console.WriteLine(ReflectionUtility.GetCollectionElementType(typeof(List<string>)));
Console.WriteLine(ReflectionUtility.GetCollectionElementType(typeof(List<int>)));
Console.WriteLine(ReflectionUtility.GetCollectionElementType(typeof(CodeNamespaceCollection)));
Console.WriteLine(ReflectionUtility.GetCollectionElementType(typeof(CodeStatementCollection)));

这应该足以使您继续使用此代码。请享用!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章