在C#中测试对象是否为泛型类型

184
我想进行一个测试,以检查一个对象是否是泛型类型。我尝试了以下方法,但没有成功:
public bool Test()
{
    List<int> list = new List<int>();
    return list.GetType() == typeof(List<>);
}

我做错了什么,我该如何进行这个测试?
6个回答

257

如果您想检查它是否是泛型类型的实例:

return list.GetType().IsGenericType;
如果您想检查它是否是通用的List<T>
return list.GetType().GetGenericTypeDefinition() == typeof(List<>);

正如Jon所指出的,这会检查精确的类型等价性。返回false并不一定意味着list 是 List<T> 返回false(即该对象不能赋值给List<T>变量)。


14
那样做无法检测到子类型。请看我的回答。对于接口来说,这也更加困难 :( - Jon Skeet
1
调用GetGenericTypeDefinition将抛出异常,如果它不是泛型类型。请确保您首先进行检查。 - Kilhoffer
@JonSkeet 你可以使用 list.GetType().BaseType 属性来检测子类型。 - mwryl
1
@MohammedLarabi:是的,这正是我的答案所做的,递归地... - Jon Skeet

106

我假设您不仅想知道类型是否是泛型,而且想知道一个对象是否是特定泛型类型的实例,而不知道类型参数。

不幸的是,这并不是非常简单。如果泛型类型是一个类(就像在这个例子中一样),它并不太困难,但对于接口来说就更难了。这里是一个类的代码:

using System;
using System.Collections.Generic;
using System.Reflection;

class Test
{
    static bool IsInstanceOfGenericType(Type genericType, object instance)
    {
        Type type = instance.GetType();
        while (type != null)
        {
            if (type.IsGenericType &&
                type.GetGenericTypeDefinition() == genericType)
            {
                return true;
            }
            type = type.BaseType;
        }
        return false;
    }

    static void Main(string[] args)
    {
        // True
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new List<string>()));
        // False
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new string[0]));
        // True
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new SubList()));
        // True
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new SubList<int>()));
    }

    class SubList : List<string>
    {
    }

    class SubList<T> : List<T>
    {
    }
}

编辑:如评论中所述,此方法适用于接口:

foreach (var i in type.GetInterfaces())
{
    if (i.IsGenericType && i.GetGenericTypeDefinition() == genericType)
    {
        return true;
    }
}

我有一种隐隐的感觉,可能在某些尴尬的边缘情况下会出问题,但是目前我找不到它失败的情况。


2
刚刚发现了一个问题。它只能沿着单一继承线路进行查找。如果在查找过程中,你有一个同时拥有基类和你要查找的接口的基础类,那么这个程序只会沿着类路径向下查找。 - Groxx
1
@Groxx:没错。不过我在回答中提到了这一点:“如果泛型类型是类(就像在这个例子中),那么情况并不太糟,但如果是接口,则会更加困难。下面是类的代码”。 - Jon Skeet
1
如果您不知道 <T> 的类型怎么办?比如说,它可能是 int 或 string,但您并不知道。这会产生虚假的负面影响...所以您没有 T 可以使用,只是在查看某个对象的属性,其中一个是列表。您如何知道它是一个列表,以便您可以解开它?我的意思是,您没有任何 T 或类型可用。您可以猜测每种类型(是 List<int> 吗?还是 List<string>?),但您想知道的是 这是一个列表吗? 这个问题似乎很难回答。 - user1086498
3
你可以将 IsInstanceOfGenericType 函数中的循环替换为调用 IsAssignableFrom 方法而不是等号(==)运算符来进行比较,这样会更好一些。 - slawekwin
请参考Weibe Tijsma的答案,他也处理了接口。 - sjb-sjb
显示剩余3条评论

13

这是我最喜欢的两种扩展方法,可以涵盖大多数泛型类型检查的边缘情况:

适用于:

  • 多个(泛型)接口
  • 多个(泛型)基类
  • 如果返回true,其中一种重载将“out”特定的泛型类型(请参见单元测试示例):

  • public static bool IsOfGenericType(this Type typeToCheck, Type genericType)
    {
        Type concreteType;
        return typeToCheck.IsOfGenericType(genericType, out concreteType); 
    }
    
    public static bool IsOfGenericType(this Type typeToCheck, Type genericType, out Type concreteGenericType)
    {
        while (true)
        {
            concreteGenericType = null;
    
            if (genericType == null)
                throw new ArgumentNullException(nameof(genericType));
    
            if (!genericType.IsGenericTypeDefinition)
                throw new ArgumentException("The definition needs to be a GenericTypeDefinition", nameof(genericType));
    
            if (typeToCheck == null || typeToCheck == typeof(object))
                return false;
    
            if (typeToCheck == genericType)
            {
                concreteGenericType = typeToCheck;
                return true;
            }
    
            if ((typeToCheck.IsGenericType ? typeToCheck.GetGenericTypeDefinition() : typeToCheck) == genericType)
            {
                concreteGenericType = typeToCheck;
                return true;
            }
    
            if (genericType.IsInterface)
                foreach (var i in typeToCheck.GetInterfaces())
                    if (i.IsOfGenericType(genericType, out concreteGenericType))
                        return true;
    
            typeToCheck = typeToCheck.BaseType;
        }
    }
    

这是一个测试,以展示(基本)功能:

 [Test]
    public void SimpleGenericInterfaces()
    {
        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IEnumerable<>)));
        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IQueryable<>)));

        Type concreteType;
        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IEnumerable<>), out concreteType));
        Assert.AreEqual(typeof(IEnumerable<string>), concreteType);

        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IQueryable<>), out concreteType));
        Assert.AreEqual(typeof(IQueryable<string>), concreteType);


    }

10

你可以使用动态代码,尽管这可能比纯反射慢,但代码会更短:

public static class Extension
{
    public static bool IsGenericList(this object o)
    {
       return IsGeneric((dynamic)o);
    }

    public static bool IsGeneric<T>(List<T> o)
    {
       return true;
    }

    public static bool IsGeneric( object o)
    {
        return false;
    }
}



var l = new List<int>();
l.IsGenericList().Should().BeTrue();

var o = new object();
o.IsGenericList().Should().BeFalse();

0
public static string WhatIsMyType<T>()
{
    return typeof(T).NameWithGenerics();
}

public static string NameWithGenerics(this Type type)
{
    if (type == null)
        throw new ArgumentNullException(nameof(type));

    if (type.IsArray)
        return $"{type.GetElementType()?.Name}[]";

    if (!type.IsGenericType) 
        return type.Name;

    var name = type.GetGenericTypeDefinition().Name;
    var index = name.IndexOf('`');
    var newName = index == -1 ? name : name.Substring(0, index);
        
    var list = type.GetGenericArguments().Select(NameWithGenerics).ToList();
    return $"{newName}<{string.Join(",", list)}>";
}

现在用这个进行测试:

Console.WriteLine(WhatIsMyType<IEnumerable<string>>());
Console.WriteLine(WhatIsMyType<List<int>>());
Console.WriteLine(WhatIsMyType<IList<int>>());
Console.WriteLine(WhatIsMyType<List<ContentBlob>>());
Console.WriteLine(WhatIsMyType<int[]>());
Console.WriteLine(WhatIsMyType<ContentBlob>());
Console.WriteLine(WhatIsMyType<Dictionary<string, Dictionary<int, int>>>());

你将会得到

IEnumerable<String>
List<Int32>
IList<Int32>
List<ContentBlob>
Int32[]
ContentBlob
Dictionary<String,Dictionary<Int32,Int32>>

0
return list.GetType().IsGenericType;

4
这个回答对于另一个问题是正确的。对于这个问题来说,它是不正确的,因为它只解决了(远少于)问题一半的部分。 - Groxx
2
Stan R的回答实际上回答了所提出的问题,但是OP真正想表达的是“在C#中测试对象是否为特定泛型类型”,对于这个问题,这个答案确实不完整。 - yoyo
人们对我进行了负评,因为我在回答问题时将其放在“is a”通用类型的上下文中,而不是“is of a”通用类型。英语是我的第二语言,这种语言细微差别常常逃过我的注意,为此辩护,OP没有特别要求针对特定类型进行测试,在标题中也只是询问“is of”通用类型...不确定为什么我会因为一个含糊不清的问题而受到负面评价。 - Stan R.
2
现在你知道了,你可以改进你的答案,使其更加具体和正确。 - Peter Ivan

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