27

I want to determine if a generic object type ("T") method type parameter is a collection type. I would typically be sending T through as a Generic.List but it could be any collection type as this is used in a helper function.

Would I be best to test if it implements IEnumerable<T>?

If so, what would the code look like?

Update 14:17 GMT+10 Possibly extending on a solution here (however only works for List<T>'s not IEnumerable<T>'s when it should if List derives ?)

T currentObj;    
// works if currentObj is List<T>
currentObj.GetType().GetGenericTypeDefinition() == typeof(List<>)
// does not work if currentObj is List<T>
currentObj.GetType().GetGenericTypeDefinition() == typeof(IEnumerable<>)
GONeale
  • 26,302
  • 21
  • 106
  • 149

12 Answers12

42

This will be the simplest check..

if(Obj is ICollection)
{
    //Derived from ICollection
}
else
{
    //Not Derived from ICollection
}
this. __curious_geek
  • 42,787
  • 22
  • 113
  • 137
  • 2
    So simple. Thanks, using your idea even 'obj is IEnumerable' and 'obj is IList' is working. I had tried 'obj is IEnumerable', 'obj is IList'.. interestingly though that did not work. I have a thought though, T would be a collection. So perhaps it's testing a list of a list somehow *shrug*. – GONeale Apr 15 '09 at 23:14
  • @GONeale: Hey this worked for me. List _ints = new List(10); if (_ints is IList) { Console.WriteLine("Yes"); } else { Console.WriteLine("No"); } – this. __curious_geek Apr 16 '09 at 11:48
  • 39
    **WARNING!** If `obj is ICollection` is true, it **does not follow** that `obj` also implements `ICollection` for any T. The interfaces `ICollection` and `ICollection` are two separate interfaces. – Timwi Aug 21 '10 at 18:25
  • so @Timwi, what would you suggest? i see no answers from you :-\ – Brad Jun 03 '14 at 20:21
  • 1
    @Brad: I was merely pointing out a possible source of confusion in this answer; I wasn’t implying that it’s _wrong_, and it appeared to solve the OP’s case, so I let it be. But since you asked, I posted a possible solution that I would consider using, but whether it’s “better” obviously depends on the use-case. – Timwi Jun 05 '14 at 12:05
  • @Timwi, cool. i also wasn't trying to troll, you just seemed to have another good idea we all weren't seeing. thanks for posting it! – Brad Jun 05 '14 at 14:35
  • it is really weird that almost all containers in Collections have implemented `ICollection` except `Hashset` only implemented `ICollection`... – Mr. Ree Jul 07 '15 at 15:11
17

You can use Type.GetInterface() with the mangled name.

private bool IsTAnEnumerable<T>(T x)
{
    return null != typeof(T).GetInterface("IEnumerable`1");
}
Jonathan Rupp
  • 15,522
  • 5
  • 45
  • 61
9

Personally I tend to use a method that I wrote myself, called TryGetInterfaceGenericParameters, which I posted below. Here is how to use it in your case:

Example of use

object currentObj = ...;  // get the object
Type[] typeArguments;
if (currentObj.GetType().TryGetInterfaceGenericParameters(typeof(IEnumerable<>), out typeArguments))
{
    var innerType = typeArguments[0];
    // currentObj implements IEnumerable<innerType>
}
else
{
    // The type does not implement IEnumerable<T> for any T
}

It is important to note here that you pass in typeof(IEnumerable<>), not typeof(IEnumerable) (which is an entirely different type) and also not typeof(IEnumerable<T>) for any T (if you already know the T, you don’t need this method). Of course this works with any generic interface, e.g. you can use typeof(IDictionary<,>) as well (but not typeof(IDictionary)).

Method source

/// <summary>
///     Determines whether the current type is or implements the specified generic interface, and determines that
///     interface's generic type parameters.</summary>
/// <param name="type">
///     The current type.</param>
/// <param name="interface">
///     A generic type definition for an interface, e.g. typeof(ICollection&lt;&gt;) or typeof(IDictionary&lt;,&gt;).</param>
/// <param name="typeParameters">
///     Will receive an array containing the generic type parameters of the interface.</param>
/// <returns>
///     True if the current type is or implements the specified generic interface.</returns>
public static bool TryGetInterfaceGenericParameters(this Type type, Type @interface, out Type[] typeParameters)
{
    typeParameters = null;

    if (type.IsGenericType && type.GetGenericTypeDefinition() == @interface)
    {
        typeParameters = type.GetGenericArguments();
        return true;
    }

    var implements = type.FindInterfaces((ty, obj) => ty.IsGenericType && ty.GetGenericTypeDefinition() == @interface, null).FirstOrDefault();
    if (implements == null)
        return false;

    typeParameters = implements.GetGenericArguments();
    return true;
}
Timwi
  • 65,159
  • 33
  • 165
  • 230
9

In order to get the actual type of T at runtime, you can use the typeof(T) expression. From there the normal type comparison operators will do the trick

bool isEnumerable = typeof(IEnumerable<int>).IsAssignableFrom(typeof(T));

Full Code Sample:

static bool Foo<T>()
{
  return typeof(IEnumerable<int>).IsAssignableFrom(typeof(T));
}

Foo<List<T>>();  // true
Foo<int>(); // false
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • What is the use of int, or was that what you meant by normal type comparison? My List would hold a custom object, but it could be anything as this is used in a Helper function. PS. I tested this code and unfortunately it returned false using int or T. – GONeale Apr 15 '09 at 04:10
  • @GONeale, int is just a place holder for the sample code. I verified this code works on my machine when T is List on my machine – JaredPar Apr 15 '09 at 04:19
  • 1
    Hmm. I am using Intermediate Window now and typeof(IEnumerable).IsAssignableFrom(typeof(T)) is returning false. For ref. T = System.Collections.Generic.List – GONeale Apr 15 '09 at 04:23
  • @GONeal, The immediate window is very different than compiling and running the code. Especially with regards to generics – JaredPar Apr 15 '09 at 04:32
  • @Jared, thanks but still no luck. Maybe I should post a complete code sample of what I am trying to do: http://bit.ly/2Bt2jI – GONeale Apr 15 '09 at 04:42
  • Thanks for your help Jared. I accepted s_ruchit's answer, I have no idea why yours didn't work, when it clearly looks like it should. Just didn't work with my custom generic list of objects. – GONeale Apr 15 '09 at 23:23
  • @GONeal, no worries. You should accept whatever answer helps you the most. – JaredPar Apr 16 '09 at 03:09
3

I would test IEnumerable instead, since a collection type could implement only IEnumerable, it doesn't have to implement IEnumerable<T>.

It also depends: what do you mean with collection type? You could have a collection without implementing any of those interfaces.

Jeroen Vannevel
  • 43,651
  • 22
  • 107
  • 170
J.W.
  • 17,991
  • 7
  • 43
  • 76
  • Well any object which can hold a list of objects. I'd truly only be using List but a generic handler would have been nicer. – GONeale Apr 15 '09 at 04:01
  • The most common types (`List`, array and `Dictionary`) all implement `IEnumerable` but with discrepancies in `IEnumerable` (the `T` in a dictionary is `KeyValuePair`). Simply checking for `IEnumerable` is thus easier and still correct. – Jeroen Vannevel Apr 04 '14 at 13:13
3

Also, remember just because you are using generics, don't forget other basic techniques, in this case, like overloading. I suspect the you are planning something like this:

void SomeFunc<T>(T t)
{
    if (IsCollectionCase(t))
       DoSomethingForCollections()
    else
       DoSOmethingElse();
}

This would be far better handled as:

void SomeFunc(IEnumerable t)
{
       DoSomethingForCollections()
}
void SomeFunc<T>(T t)
{
       DoSomethingElse()
}
James Curran
  • 101,701
  • 37
  • 181
  • 258
  • Thanks for the comments James, yep normally I would and an overload would do the trick, but only doing some simple collection adding and private variable changes if it is a list, so no need for any additional function calls. Cheers. – GONeale Apr 15 '09 at 04:00
  • This will not work! For example a List will satisfy both overrides so the compiler doesn't know which one to choose. At least for .Net Core / Roslyn, it chooses the second override. – kjbartel Dec 20 '18 at 09:34
2

While I can't be certain what the original poster's intent was, there have been several responses to the effect of casting to IEnumerable for testing. That's fine, but everyone should be aware that string instances pass this test, which may not be something the original author intended. I know I certainly didn't when I went looking for an answer and found this post:

string testString = "Test";
Console.WriteLine(testString as IEnumerable != null);  // returns true

I am in the process of trying to write a custom serializer that uses reflection to accomplish certain tasks. As part of a task, I need to determine if a property value is a collection/array/list of items or a single property value. What is particularly annoying is that several Linq expressions actually result in an enumerable type value, but GetType().IsArray returns false for these, and casting them to ICollection returns null as well, but casting them to IEnumerable returns a non-null value.

So...for the time being, I am still seeking a solution that works for all cases.

Mitselplik
  • 1,079
  • 1
  • 12
  • 16
1

For simplicity and code sharing, I usually use this extension method:

public static bool IsGenericList(this object obj)
{
    return IsGenericList(obj.GetType());
}

public static bool IsGenericList(this Type type)
{
    if (type == null)
    {
        throw new ArgumentNullException("type");
    }

    foreach (Type @interface in type.GetInterfaces())
    {
        if (@interface.IsGenericType)
        {
            if (@interface.GetGenericTypeDefinition() == typeof(ICollection<>))
            {
                // if needed, you can also return the type used as generic argument
                return true;
            }
        }
    }

    return (type.GetInterface("IEnumerable") != null);
}
Eduard Malakhov
  • 1,095
  • 4
  • 13
  • 25
Chipo Hamayobe
  • 877
  • 11
  • 13
1

If you want to do a check and get true for any list/collection/IEnumerable, but get false for type of string, then

   private static bool IsIEnumerable(Type requestType)
   {
      var isIEnumerable = typeof(IEnumerable).IsAssignableFrom(requestType);
      var notString = !typeof(string).IsAssignableFrom(requestType);
      return isIEnumerable && notString;
   }
Elvis Skensberg
  • 151
  • 1
  • 9
0

I love generics. In this method T must have a public and parameterless constructor which means you can not use IList<object> for T. You must use List<object>

public static T IsEnumerable<T>() where T : new() {
    if (new T() is IEnumerable) {
}
guneysus
  • 6,203
  • 2
  • 45
  • 47
0

I came across the same issue while attempting to serialize any object to JSON format. Here is what I ended up using:

Type typ = value.GetType();

// Check for array type
if(typeof(IEnumerable).IsAssignableFrom(typ) || typeof(IEnumerable<>).IsAssignableFrom(typ))
{ 
    List<object> list = ((IEnumerable)value).Cast<object>().ToList();
    //Serialize as an array with each item in the list...
}
else
{
    //Serialize as object or value type...
}
k rey
  • 611
  • 4
  • 11
0

For an ICollection of any type (List or HashSet for example):

internal static bool IsCollection(this Type type) => type.GetGenericArguments().Length > 0 && (typeof(ICollection<>).MakeGenericType(type.GetGenericArguments()[0])).IsAssignableFrom(type);
Tod
  • 2,070
  • 21
  • 27