如何判断GenericTypeDefinition是否实现了IEnumerable<>?

7

我有一个方法,可以检查类型是否为泛型,然后检查GenericTypeDefinition是否为 IEnumerable<>

static Type GetEnumerableType(Type type)  
{  
    if(type.IsGenericType) {  
        var genericTypeDefinition = type.GetGenericTypeDefinition();  
        if (genericTypeDefinition == typeof(IEnumerable<>)) {  
            return type.GetGenericArguments()[0];  
        }  
    }  
    return null;  
}

如果它是IEnumerable类型,那么它的工作效果就像魔术一样。但如果GenericTypeDefinition是IList<>List<>,它就无法正常工作。我已经尝试过了。

typeof(IEnumerable<>).IsAssignableFrom(genericTypeDefinition)

...没有成功。当然,肯定有比链接else语句更好的方法吧?


1
我认为Stan R.的解决方案可能大致符合您的要求,但是您的问题未明确说明。您只关心通用类型,还是也关心非通用类型(例如实现IEnumerable<string>的非通用类型)? - kvb
目前我只关心泛型,但你说得很有道理。 - Kenny Eliasson
2个回答

14
您可以使用GetInterfaces来检查一个类型是否实现了IEnumerable<>,如下所示。
Type type = new List<string>().GetType();

if (type.IsGenericType) 
{
    var genericTypeDefinition = type.GetGenericTypeDefinition();

    if (genericTypeDefinition.GetInterfaces()
                .Any( t => t.IsGenericType && 
                           t.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
    {
        return type.GetGenericArguments()[0];
    }
}

1
由于MyList:List<string>yield return @string不是泛型类,因此该方法会错误地返回false,因为MyList和yield生成的状态机不是泛型类,而IsGenericType为false。 - Chris Marisic

7

我深入研究了一般对象处理,发现这比任何原始假设都更为复杂。这是我现在使用的方法:

/// <summary>Check whether the specified type is enumerable.</summary>
/// <param name="type">The type.</param>
/// <param name="underlyingType">IEnumerable{int} would be int</param>
/// <param name="excludeString">
///  [OPTIONAL] if set to <c>true</c> [exclude string]. Strings are enumerable as char[]
///  this is likely not something you want. Default is true (string will return false)
/// </param>
/// <returns><c>true</c> supplied type is enumerable otherwise <c>false</c></returns>
public static bool IsEnumerable(this Type type, out Type underlyingType,
                                bool excludeString = true)
{
    underlyingType = null;

    if (type.IsEnum || type.IsPrimitive || type.IsValueType) return false;

    if (excludeString && type == typeof(string)) return false;

    if (type.IsGenericType)
    {
        if (type.IsTypeDefinitionEnumerable() ||
            type.GetInterfaces()
                .Any(t => t.IsSelfEnumerable() || t.IsTypeDefinitionEnumerable()))
        {
            underlyingType = type.GetGenericArguments()[0];
            return true;
        }
    }
    //direct implementations of IEnumerable<T>, inheritance from List<T> etc
    var enumerableOrNull = type.GetInterfaces()
                               .FirstOrDefault(t => t.IsTypeDefinitionEnumerable());
    if (enumerableOrNull == null) return false;

    underlyingType = enumerableOrNull.GetGenericArguments()[0];
    return true;
}

//

private static bool IsSelfEnumerable(this Type type)
{
    bool isDirectly = type == typeof(IEnumerable<>);
    return isDirectly;
}

private static bool IsTypeDefinitionEnumerable(this Type type)
{
    bool isViaInterfaces = type.IsGenericType && 
                           type.GetGenericTypeDefinition().IsSelfEnumerable();
    return isViaInterfaces;
}

这个解决方案已经过测试:

安装NUnit包 - 版本2.6.4

安装Shouldly包

[Test]
public void List_is_enumerable()
{
    var sut = new List<int>();

    Type underlyingType;
    var result = sut.IsEnumerable(out underlyingType);

    result.ShouldBeTrue();
    underlyingType.ShouldBe(typeof(int));
}

//

[Test]
public void Yield_return_is_enumerable()
{
    var sut = Yielded();

    Type underlyingType;
    var result = sut.IsEnumerable(out underlyingType);

    result.ShouldBeTrue();
    underlyingType.ShouldBe(typeof(int));
}

private IEnumerable<int> Yielded()
{
    for (int i = 0; i < 3; i++)
    {
        yield return i;
    }
}

//

[Test]
public void int_is_not_an_enumerable()
{
    var sut = 5;

    Type underlyingType;
    var result = sut.IsEnumerable(out underlyingType);

    result.ShouldBe(false);
    underlyingType.ShouldBeNull();
}

[Test]
public void object_is_not_an_enumerable()
{
    var sut = new { foo = 1};

    Type underlyingType;
    var result = sut.IsEnumerable(out underlyingType);

    result.ShouldBe(false);
    underlyingType.ShouldBeNull();
}

保存供后人参考。虽然这并没有回答原问题,但显然对在场的成员很有帮助。
public static bool IsA<T>(this Type type)
{
    return typeof (T).IsAssignableFrom(type);
}

这并没有解决问题。是的,我也经常混淆IsAssignableFrom方法,但我不认为这是这种情况。 - Kenny Eliasson
1
你使用 if(type.IsA<IEnumerable<>>) { 编辑代码无法编译,因为它需要一个类型参数。 - Kenny Eliasson
1
我会挑剔一下,把这个方法命名为“IsOfType”之类的。为什么呢?因为“Is a IEnumerable”听起来不太对(应该是“Is an”,但这样就变得非常棘手了:P)说真的,我要借鉴这个扩展方法。我喜欢! - Erik van Brakel
OP并没有错误地使用IsAssignableFrom,他只是在一个开放式泛型类型上使用了它。此外,根据你的代码编写方式,type.IsA<IEnumerable<>>()将对所有类型返回false。 - Tinister
完全重写的答案 - Chris Marisic
回顾一下,您可能想使用一个 static ConcurrentDictionary<Type,Type> 来缓存 if (type.IsGenericType) ... 等操作的结果。 - Chris Marisic

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