目录
介绍
概念化这个混乱
编码此混乱
- 下载实用程序-5.7 KB
通过这个技巧,我将努力向读者展示如何使用反射来查询一个集合类的元素类型。当涉及到未实现IEnumerable的类型化集合时,一开始看起来相对简单的事情很快就变得复杂起来了。在.NET 2.0之前创建的类型化集合就是这种情况。这些集合非常普遍。例如,许多Windows窗体以及CodeDOM都是这样的集合的类型。这些集合更需要获取元素类型。
概念化这个混乱有时我们可能需要通过反射来获取集合的元素类型。我通常在编写代码生成器时遇到此问题。这对于.NET post 1.1而言是微不足道的,但是在此之前,由于无法创建可以处理该类型的通用接口,因此没有用于类型化集合的标准接口。
为了获得通用的集合类型,在这种情况下,我们要做的就是查询IEnumerable接口,然后返回任何T内容。简单易用,它也适用于词典。
不幸的是,为了获得非泛型的集合元素类型,我们必须使用一些启发式方法。
我们要做的第一件事是查询IDictionary接口。如果找到它,我们将返回DictionaryEntry。
如果没有结果,接下来我们进行查询IList,如果找到它,我们将寻找一个采用单个整数参数并返回除object以外的其他类型的公共索引器属性。
最后,如果找不到,我们查找ICollection,并查找具有单个参数的Add()方法,该参数不是object类型的。我发现这是确定集合的元素类型的最可靠方法。
最后,如果那行不通,我们寻找IEnumerable并找到它,然后返回object类型。否则,我们返回null表明它不是集合类型。我们可以在枚举器的IEnumerator接口上查询Current属性,但是,没有可靠的方法可以在不调用实例上的GetEnumerator()的情况下获得枚举数的类型。在实践中,我认为我没有看到太多不是泛型的类型化枚举器实现。
编码此混乱像我通常所做的那样,我将几乎完整地发布代码,然后从上至下进行处理:
static partial class ReflectionUtility
{
///
/// Indicates whether or not the specified type is a list.
///
/// The type to query
/// True if the type is a list, otherwise false
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;
}
///
/// Retrieves the collection element type from this type
///
/// The type to query
/// The element type of the collection or null if the type was not a collection
///
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 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)));
Console.WriteLine(ReflectionUtility.GetCollectionElementType(typeof(List)));
Console.WriteLine(ReflectionUtility.GetCollectionElementType(typeof(CodeNamespaceCollection)));
Console.WriteLine(ReflectionUtility.GetCollectionElementType(typeof(CodeStatementCollection)));
这应该足以使您继续使用此代码。请享用!