关于c#:如何检测类型是否为另一种通用类型

关于c#:如何检测类型是否为另一种通用类型

How To Detect If Type is Another Generic Type

例:

1
2
3
4
5
public static void DoSomething<K,V>(IDictionary<K,V> items) {
   items.Keys.Each(key => {
      if (items[key] **is IEnumerable< ? >**) { /* do something */ }
      else { /* do something else */ }
}

不用反射就可以做到吗? 我该怎么说C#中的IEnumerable? 由于IEnumerable <>实现了IEnumerable,我应该只使用IEnumerable吗?


非常感谢这篇文章。 我想提供一个对我更好的Konrad Rudolph解决方案版本。 我对该版本有一些小问题,尤其是在测试Type是否为可为空的值类型时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static bool IsAssignableToGenericType(Type givenType, Type genericType)
{
    var interfaceTypes = givenType.GetInterfaces();

    foreach (var it in interfaceTypes)
    {
        if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType)
            return true;
    }

    if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
        return true;

    Type baseType = givenType.BaseType;
    if (baseType == null) return false;

    return IsAssignableToGenericType(baseType, genericType);
}

先前接受的答案很好,但这是错误的。值得庆幸的是,该错误很小。如果您真的想了解接口的通用版本,仅检查IEnumerable是不够的;有很多只实现非通用接口的类。一分钟后,我会给出答案。不过,首先,我想指出,所接受的答案过于复杂,因为在给定的情况下,以下代码将实现相同的效果:

1
if (items[key] is IEnumerable)

这做得更多,因为它分别适用于每个项目(而不适用于它们的公共子类V)。

现在,寻找正确的解决方案。这有点复杂,因为我们必须采用通用类型IEnumerable`1(即带有一个类型参数的类型IEnumerable<>)并注入正确的通用参数:

1
2
3
4
5
6
7
8
static bool IsGenericEnumerable(Type t) {
    var genArgs = t.GetGenericArguments();
    if (genArgs.Length == 1 &&
            typeof(IEnumerable<>).MakeGenericType(genArgs).IsAssignableFrom(t))
        return true;
    else
        return t.BaseType != null && IsGenericEnumerable(t.BaseType);
}

您可以轻松测试此代码的正确性:

1
2
3
4
var xs = new List<string>();
var ys = new System.Collections.ArrayList();
Console.WriteLine(IsGenericEnumerable(xs.GetType()));
Console.WriteLine(IsGenericEnumerable(ys.GetType()));

产量:

1
2
True
False

不要太在意使用反射的事实。的确,这增加了运行时开销,使用is运算符也是如此。

当然,上面的代码受到了严格的限制,可以扩展为更通用的方法IsAssignableToGenericType。以下实施方式有些不正确1,我将其留在此处仅出于历史目的。不要使用它。取而代之的是,James在回答中提供了一个出色而正确的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static bool IsAssignableToGenericType(Type givenType, Type genericType) {
    var interfaceTypes = givenType.GetInterfaces();

    foreach (var it in interfaceTypes)
        if (it.IsGenericType)
            if (it.GetGenericTypeDefinition() == genericType) return true;

    Type baseType = givenType.BaseType;
    if (baseType == null) return false;

    return baseType.IsGenericType &&
        baseType.GetGenericTypeDefinition() == genericType ||
        IsAssignableToGenericType(baseType, genericType);
}

1当genericTypegivenType相同时失败;出于相同的原因,它对于可为空的类型也失败,即

1
2
IsAssignableToGenericType(typeof(List<int>), typeof(List<>)) == false
IsAssignableToGenericType(typeof(int?), typeof(Nullable<>)) == false

我创建了一个包含一整套测试用例的要点。


关于泛型类型和使用IsAssignableFrom()的警告。

说您有以下几点:

1
2
3
4
5
6
7
8
9
10
11
public class MyListBase< T > : IEnumerable< T > where T : ItemBase
{
}

public class MyItem : ItemBase
{
}

public class MyDerivedList : MyListBase<MyItem>
{
}

在基本列表类型或派生列表类型上调用IsAssignableFrom将返回false,但显然MyDerivedList继承了MyListBase< T >。 (对Jeff的快速说明,泛型绝对必须包装在代码块或波浪号中才能得到< T >,否则将省略。这是故意的吗?)问题源于以下事实:MyListBase被视为完全不同键入MyListBase< T >。下面的文章可以更好地解释这一点。 http://mikehadlow.blogspot.com/2006/08/reflecting-generics.html

而是尝试以下递归函数:

1
2
3
4
5
6
7
8
9
10
    public static bool IsDerivedFromGenericType(Type givenType, Type genericType)
    {
        Type baseType = givenType.BaseType;
        if (baseType == null) return false;
        if (baseType.IsGenericType)
        {
            if (baseType.GetGenericTypeDefinition() == genericType) return true;
        }
        return IsDerivedFromGenericType(baseType, genericType);
    }

/编辑:Konrad的新文章充分考虑了通用递归以及接口。很好:)

/ EDIT2:如果检查genericType是否为接口,则可以实现性能优势。该检查可以是当前接口代码周围的if块,但是如果您对使用.NET 3.5感兴趣,我的一个朋友可以提供以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
    public static bool IsAssignableToGenericType(Type givenType, Type genericType)
    {
        var interfaces = givenType.GetInterfaces().Where(it => it.IsGenericType).Select(it => it.GetGenericTypeDefinition());
        var foundInterface = interfaces.FirstOrDefault(it => it == genericType);
        if (foundInterface != null) return true;

        Type baseType = givenType.BaseType;
        if (baseType == null) return false;

        return baseType.IsGenericType ?
            baseType.GetGenericTypeDefinition() == genericType :
            IsAssignableToGenericType(baseType, genericType);
    }

感谢您提供的信息。 为了方便起见,我将其重构为扩展方法,并将其简化为单个语句。

1
2
3
4
5
6
public static bool IsAssignableToGenericType(this Type givenType, Type genericType)
{
  return givenType.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == genericType) ||
         givenType.BaseType != null && (givenType.BaseType.IsGenericType && givenType.BaseType.GetGenericTypeDefinition() == genericType ||
                                        givenType.BaseType.IsAssignableToGenericType(genericType));
}

现在可以使用以下命令轻松调用它:

sometype.IsAssignableToGenericType(typeof(MyGenericType <>))


1
if (typeof(IEnumerable).IsAssignableFrom(typeof(V))) {

我会使用重载:

1
2
3
4
5
6
7
8
9
10
public static void DoSomething<K,V>(IDictionary<K,V> items)
  where V : IEnumerable
{
   items.Keys.Each(key => { /* do something */ });
}

public static void DoSomething<K,V>(IDictionary<K,V> items)
{
   items.Keys.Each(key => { /* do something else */ });
}

我曾经认为,这种情况可以通过类似于@Thomas Danecker的解决方案来解决,但是增加了另一个模板参数:

1
2
3
4
public static void DoSomething<K, V, U>(IDictionary<K,V> items)
    where V : IEnumerable<U> { /* do something */ }
public static void DoSomething<K, V>(IDictionary<K,V> items)
                             { /* do something else */ }

但是我现在注意到,除非我明确指定第一个方法的模板参数,否则它将无法正常工作。显然,这不是针对字典中的每个项目定制的,但它可能是穷人的一种解决方案。

如果有人可以指出我在这里所做的任何错误,我将非常感谢。


我不确定我是否理解您的意思。您是否想知道对象是任何通用类型还是要测试它是否是特定的通用类型?还是只想知道是否可以枚举?

我认为第一个不可能。第二种绝对是可能的,只需将其视为任何其他类型。对于第三个,按照您的建议对IEnumerable进行测试。

同样,您不能在类型上使用'is'运算符。

1
2
3
4
5
6
// Not allowed
if (string is Object)
  Foo();
// You have to use
if (typeof(object).IsAssignableFrom(typeof(string))
  Foo();

有关更多详细信息,请参见有关类型的问题。也许会对您有帮助。


您想签出Type.IsInstanceOfType方法


推荐阅读