7

I have a method that checks if a type is generic and then checks if the GenericTypeDefinition is of IEnumerable<>.

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

Works like a charm if it is an IEnumerable. If the the GenericTypeDefinition is IList<> or List<> it doesn't work. I've tried..

typeof(IEnumerable<>).IsAssignableFrom(genericTypeDefinition)

..without success. Of course there must be a better way then chaining else-statements?

Jon Cage
  • 36,366
  • 38
  • 137
  • 215
Kenny Eliasson
  • 2,047
  • 15
  • 23
  • 1
    I think that Stan R.'s solution is probably roughly what you're looking for, but your problem is underspecified. Do you only care about generic types, or might you also care about non-generic types (e.g. a non-generic type implementing `IEnumerable`)? – kvb Feb 23 '10 at 16:14
  • At the momement I only care about generic types but you make a good point. – Kenny Eliasson Feb 23 '10 at 16:16

2 Answers2

14

You can use GetInterfaces to check if a type implements IEnumerable<> like so

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];
    }
}
Chris Marisic
  • 32,487
  • 24
  • 164
  • 258
Stan R.
  • 15,757
  • 4
  • 50
  • 58
  • 1
    This method will errantly return false for `MyList : List` or `yield return @string` since `MyList` and the state machine generated by yield are not generic classes and `IsGenericType` is false – Chris Marisic May 23 '16 at 19:57
7

I ended diving way deeper into general object handling and found this way more complex than any original assumptions. This is the method I'm now using:

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

This solution is tested:

Install-Package NUnit -Version 2.6.4

Install-Package 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();
}

Held for posterity. This doesn't answer the original question but was clearly useful to members here.

public static bool IsA<T>(this Type type)
{
    return typeof (T).IsAssignableFrom(type);
}
Community
  • 1
  • 1
Chris Marisic
  • 32,487
  • 24
  • 164
  • 258
  • It doesn't solve the problem though. And, yes, I also often mistake the IsAssignableFrom method, but I don't think thats the case here. – Kenny Eliasson Feb 23 '10 at 16:05
  • 1
    Your edit code with `if(type.IsA>) { ` wont compile cause it wants a type argument. – Kenny Eliasson Feb 23 '10 at 16:14
  • 1
    I'd be a nitpicker and call the method something like 'IsOfType'. Why? Because 'Is a IEnumerable' doesn't sound right (should be Is an, but then it gets very tricky :P) In all seriousness though, I'm going to steal this extensionmethod. I like! – Erik van Brakel Feb 23 '10 at 16:14
  • The OP wasn't using `IsAssignableFrom` backwards, he was just using it on an [open generic type](http://stackoverflow.com/a/1735059/34715). Moreover, with the way your code is written, `type.IsA>()` will return false with all types. – Tinister Oct 02 '15 at 23:47
  • Completely rewritten answer – Chris Marisic May 20 '16 at 19:14
  • Looking back at this, you probably want to use a `static ConcurrentDictionary` to cache the efforts of `if (type.IsGenericType) ...` et al – Chris Marisic Aug 31 '20 at 14:36