目錄
介紹
通過這個技巧,我將努力向讀者展示如何使用反射來查詢一個集合類的元素類型。當涉及到未實現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)));
這應該足以使您繼續使用此代碼。請享用!