从IEnumerable<T>获取类型T

124
有没有办法通过反射从IEnumerable中检索类型T?
例如,我有一个变量IEnumerable info; 我想通过反射来检索Child的类型。

1
在什么上下文中?这个IEnumerable<T>是什么?它是作为参数发送的对象实例吗?还是其他什么? - Mehrdad Afshari
13个回答

169
IEnumerable<T> myEnumerable;
Type type = myEnumerable.GetType().GetGenericArguments()[0]; 

因此,
IEnumerable<string> strings = new List<string>();
Console.WriteLine(strings.GetType().GetGenericArguments()[0]);

打印 System.String

请查看 MSDN 上的 Type.GetGenericArguments

编辑:我相信这将解决评论中的问题:

// returns an enumeration of T where o : IEnumerable<T>
public IEnumerable<Type> GetGenericIEnumerables(object o) {
    return o.GetType()
            .GetInterfaces()
            .Where(t => t.IsGenericType
                && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            .Select(t => t.GetGenericArguments()[0]);
}

有些对象实现了不止一个通用的 IEnumerable 接口,因此需要返回它们的枚举。

编辑:虽然我必须说,一个类实现多个 TIEnumerable<T> 接口是一个糟糕的想法。


1
或者尝试使用以下类定义:MyClass:IEnumerable<int> {}。这个类没有泛型接口。 - Stefan Steinegger
1
为什么有人会采用获取通用参数,然后从其索引器中获取类型的方法呢?这只是在寻求灾难,特别是当语言支持像@amsprich在他/她的答案中建议的typeof(T)时,它可以与通用或已知类型一起使用... - Robert Petz
1
当与linq查询一起使用时,这种方法会彻底失败——WhereSelectEnumerableIterator的第一个泛型参数是不是接口本身,而是基础对象的泛型参数。 - Pxtl
2
当您使用数组时,此方法会失败,因为它强制转换为IEnumerable。 - Jono
1
对于数组,您可以使用GetType().GetElementType()。 - Jono
显示剩余2条评论

42
我只需创建一个扩展方法。这对我试过的所有东西都有效。
public static Type GetItemType<T>(this IEnumerable<T> enumerable)
{
    return typeof(T);
}

11
如果您的编译时引用仅为对象类型,则无法正常工作。 - Stijn Van Antwerpen

36

我曾经遇到过类似的问题。所选答案适用于实际情况。

在我的情况下,我只有一个类型(来自PropertyInfo)。

当类型本身是typeof(IEnumerable<T>)而不是IEnumerable<T>的实现时,所选答案会失败。

对于这种情况,以下方法有效:

public static Type GetAnyElementType(Type type)
{
   // Type is Array
   // short-circuit if you expect lots of arrays 
   if (type.IsArray)
      return type.GetElementType();

   // type is IEnumerable<T>;
   if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof (IEnumerable<>))
      return type.GetGenericArguments()[0];

   // type implements/extends IEnumerable<T>;
   var enumType = type.GetInterfaces()
                           .Where(t => t.IsGenericType && 
                                  t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                           .Select(t => t.GenericTypeArguments[0]).FirstOrDefault();
   return enumType ?? type;
}

挽救了我的一天。对于我的情况,我添加了一个单独的if语句来处理字符串,因为它实现了IEnumerable<char>。 - Edmund P Charumbira
Type.GenericTypeArguments - 仅适用于 dotNet Framework 版本 >= 4.5。 否则,请使用 Type.GetGenericArguments - Кое Кто

21

如果您了解 IEnumerable<T>(通常使用泛型),那么只需要使用 typeof(T) 即可。否则(对于 object 或非泛型的 IEnumerable),请检查实现的接口:

        object obj = new string[] { "abc", "def" };
        Type type = null;
        foreach (Type iType in obj.GetType().GetInterfaces())
        {
            if (iType.IsGenericType && iType.GetGenericTypeDefinition()
                == typeof(IEnumerable<>))
            {
                type = iType.GetGenericArguments()[0];
                break;
            }
        }
        if (type != null) Console.WriteLine(type);

3
有些对象实现了不止一个通用的 IEnumerable 接口。 - jason
5
@Jason - 在那些情况下,“找到T”这个问题本身就是一个值得怀疑的问题;我无法改变这一点... - Marc Gravell
对于任何试图使用 Type type 参数而不是 object obj 参数的人来说,有一个小问题:你不能仅仅用 type 替换 obj.GetType(),因为如果你传入 typeof(IEnumerable<T>),你将得到空值。为了解决这个问题,测试 type 本身是否是 IEnumerable<> 的泛型,然后再测试它的接口。 - Ian Mercer

11
非常感谢讨论。我以此为基础编写了下面的解决方案,它可以很好地处理我所关心的所有情况(包括IEnumerable、派生类等)。我认为应该在这里分享一下,以防其他人也需要:

非常感谢讨论。我以此为基础编写了下面的解决方案,它可以很好地处理我所关心的所有情况(包括IEnumerable、派生类等)。我认为应该在这里分享一下,以防其他人也需要:

  Type GetItemType(object someCollection)
  {
    var type = someCollection.GetType();
    var ienum = type.GetInterface(typeof(IEnumerable<>).Name);
    return ienum != null
      ? ienum.GetGenericArguments()[0]
      : null;
  }

1
以下是一行代码,使用空值条件运算符完成所有操作:someCollection.GetType().GetInterface(typeof(IEnumerable<>).Name)?.GetGenericArguments()?.FirstOrDefault() - Mass Dot Net

3

我知道这有点老旧,但我相信这种方法可以解决在评论中提到的所有问题和挑战。感谢Eli Algranti给我的启示。

/// <summary>Finds the type of the element of a type. Returns null if this type does not enumerate.</summary>
/// <param name="type">The type to check.</param>
/// <returns>The element type, if found; otherwise, <see langword="null"/>.</returns>
public static Type FindElementType(this Type type)
{
   if (type.IsArray)
      return type.GetElementType();

   // type is IEnumerable<T>;
   if (ImplIEnumT(type))
      return type.GetGenericArguments().First();

   // type implements/extends IEnumerable<T>;
   var enumType = type.GetInterfaces().Where(ImplIEnumT).Select(t => t.GetGenericArguments().First()).FirstOrDefault();
   if (enumType != null)
      return enumType;

   // type is IEnumerable
   if (IsIEnum(type) || type.GetInterfaces().Any(IsIEnum))
      return typeof(object);

   return null;

   bool IsIEnum(Type t) => t == typeof(System.Collections.IEnumerable);
   bool ImplIEnumT(Type t) => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>);
}

3
public static Type GetInnerGenericType(this Type type)
{
  // Attempt to get the inner generic type
  Type innerType = type.GetGenericArguments().FirstOrDefault();

  // Recursively call this function until no inner type is found
  return innerType is null ? type : innerType.GetInnerGenericType();
}

这是一个递归函数,将深度优先遍历泛型类型列表,直到找到没有内部泛型类型的具体类型定义。
我使用以下类型进行了测试: ICollection<IEnumerable<ICollection<ICollection<IEnumerable<IList<ICollection<IEnumerable<T>>>>>>>> 该方法应返回 T

2

在简单情况下,可以使用另一种替代方法,即要么是 IEnumerable<T>,要么是 T - 注意使用 GenericTypeArguments 而不是 GetGenericArguments()

Type inputType = o.GetType();
Type genericType;
if ((inputType.Name.StartsWith("IEnumerable"))
    && ((genericType = inputType.GenericTypeArguments.FirstOrDefault()) != null)) {

    return genericType;
} else {
    return inputType;
}

2

只需使用 typeof(T)

编辑: 如果您没有 T,请在实例化对象上使用 .GetType().GetGenericParameter()。


如果是True,您可以使用.GetType()。我会修改我的答案。 - rein

1
这是对Eli Algranti解决方案的改进,它也适用于在继承树的任何级别上使用IEnumerable<>类型。
此解决方案将从任何Type中获取元素类型。如果类型不是IEnumerable<>,则会返回传递的类型。对于对象,请使用GetType。对于类型,请使用typeof,然后在结果上调用此扩展方法。
public static Type GetGenericElementType(this Type type)
{
    // Short-circuit for Array types
    if (typeof(Array).IsAssignableFrom(type))
    {
        return type.GetElementType();
    }

    while (true)
    {
        // Type is IEnumerable<T>
        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        {
            return type.GetGenericArguments().First();
        }

        // Type implements/extends IEnumerable<T>
        Type elementType = (from subType in type.GetInterfaces()
            let retType = subType.GetGenericElementType()
            where retType != subType
            select retType).FirstOrDefault();

        if (elementType != null)
        {
            return elementType;
        }

        if (type.BaseType == null)
        {
            return type;
        }

        type = type.BaseType;
    }
}

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