如何在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)));

這應該足以使您繼續使用此代碼。請享用!

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