如何检测类型是否是另一种通用类型

时间:2008-09-16 17:07:54

标签: c#

示例:

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&lt;&gt;实现IEnumerable?

9 个答案:

答案 0 :(得分:102)

非常感谢这篇文章。我想提供一个对我来说效果更好的Konrad Rudolph解决方案。我在该版本中遇到了一些小问题,特别是在测试Type是否为可空值类型时:

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);
}

答案 1 :(得分:40)

The previously accepted answer很不错,但这是错误的。值得庆幸的是,错误很小。如果您真的想了解接口的通用版本,则检查IEnumerable是不够的;有很多类只实现非泛型接口。我会在一分钟内给出答案。首先,我想指出接受的答案过于复杂,因为以下代码在特定情况下会达到相同的效果:

if (items[key] is IEnumerable)

这甚至更多,因为它分别适用于每个项目(而不是它们的共同子类V)。

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

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);
}

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

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

的产率:

True
False

不要过分担心这会使用反射。虽然这确实会增加运行时间开销,但使用is运算符也是如此。

当然上面的代码非常受限制,可以扩展为更普遍适用的方法IsAssignableToGenericType。以下实现稍微不正确 1 ,我将其留在仅用于历史目的不要使用。相反,James has provided an excellent, correct implementation in his answer.

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相同时失败;出于同样的原因,它无法用于可空类型,即

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

我创建了gist with a comprehensive suite of test cases

答案 2 :(得分:5)

关于泛型类型和使用IsAssignableFrom()...

的警告

说你有以下内容:

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<MyItem>被视为与MyListBase<T>完全不同的类型。以下文章可以更好地解释这一点。 http://mikehadlow.blogspot.com/2006/08/reflecting-generics.html

相反,请尝试以下递归函数:

    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感兴趣,我的一位朋友提供以下内容:

    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);
    }

答案 3 :(得分:4)

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

答案 4 :(得分:4)

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

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&LT;&GT))

答案 5 :(得分:1)

我会使用重载:

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 */ });
}

答案 6 :(得分:0)

我不确定我明白你的意思。您想知道对象是任何泛型类型还是要测试它是否是特定泛型类型?或者你只是想知道它是否可以枚举?

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

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

// Not allowed
if (string is Object)
  Foo();
// You have to use 
if (typeof(object).IsAssignableFrom(typeof(string))
  Foo();

有关详细信息,请参阅this question about types。也许它会帮助你。

答案 7 :(得分:0)

您想要查看Type.IsInstanceOfType方法

答案 8 :(得分:0)

我曾经认为这样的情况可以通过类似于@Thomas Danecker solution的方式解决,但添加了另一个模板参数:

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 */ }

但我现在注意到它不起作用,除非我明确指定第一个方法的模板参数。这显然不是根据字典中的每个项目定制的,但它可能是一种穷人的解决方案。

如果有人能指出我在这里可能做的任何不正确的事情,我将非常感激。