35

example:

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

Can this be done without using reflection? How do I say IEnumerable in C#? Should I just use IEnumerable since IEnumerable<> implements IEnumerable?

Chris Bilson
  • 1,884
  • 1
  • 17
  • 20

9 Answers9

145

Thanks very much for this post. I wanted to provide a version of Konrad Rudolph's solution that has worked better for me. I had minor issues with that version, notably when testing if a Type is a nullable value 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);
}
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
James Fraumeni
  • 1,451
  • 2
  • 9
  • 2
  • 1
    Great work! Here's a short(er) version for people who prefer brevity: `public static bool IsAssignableToGenericType(this Type type, Type genericType) { return type.GetInterfaces().Any(it => it.IsGenericType && it.GetGenericTypeDefinition() == genericType) || (type.IsGenericType && type.GetGenericTypeDefinition() == genericType) || (type.BaseType != null && IsAssignableToGenericType(type.BaseType, genericType)); }` – Moeri Nov 23 '14 at 12:53
  • 3
    This is great but it seems like this would be much faster if you moved the direct comparison to be above the interface comparison if the direct comparison matched? – Shazwazza Feb 03 '15 at 05:01
  • 1
    @Moeri Make it even shorter by eliminating the redundant predicate and using `?.`: `public static bool IsAssignableToGenericType( this Type type, Type generic ) { return new[] { type }.Concat( type.GetTypeInfo().ImplementedInterfaces ).Any( i => i.IsConstructedGenericType && i.GetGenericTypeDefinition() == generic ) || (type.GetTypeInfo().BaseType?.IsAssignableToGenericType( generic ) ?? false); }` – HappyNomad Aug 25 '16 at 02:22
  • I think checking the `interfaceTypes` when the method uses recursion (meaning the last line of code is hit, which checks the `baseType`) is wasteful? `GetInterfaces` returns all the interfaces implemented or inherited, so that check isn't necessary for BaseType right? – Suraj Dec 02 '19 at 19:30
42

The previously accepted answer is nice but it is wrong. Thankfully, the error is a small one. Checking for IEnumerable is not enough if you really want to know about the generic version of the interface; there are a lot of classes that implement only the nongeneric interface. I'll give the answer in a minute. First, though, I'd like to point out that the accepted answer is overly complicated, since the following code would achieve the same under the given circumstances:

if (items[key] is IEnumerable)

This does even more because it works for each item separately (and not on their common subclass, V).

Now, for the correct solution. This is a bit more complicated because we have to take the generic type IEnumerable`1 (that is, the type IEnumerable<> with one type parameter) and inject the right generic argument:

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

You can test the correctness of this code easily:

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

yields:

True
False

Don't be overly concerned by the fact that this uses reflection. While it's true that this adds runtime overhead, so does the use of the is operator.

Of course the above code is awfully constrained and could be expanded into a more generally applicable method, IsAssignableToGenericType. The following implementation is slightly incorrect1 and I’ll leave it here for historic purposes only. Do not use it. Instead, 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 It fails when the genericType is the same as givenType; for the same reason, it fails for nullable types, i.e.

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

I’ve created a gist with a comprehensive suite of test cases.

Community
  • 1
  • 1
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • This is exactly what I was looking for. Thanks! It still uses reflection, but I was looking for the "typeof(IEnumerable<>)" syntax. Thanks! – Chris Bilson Sep 16 '08 at 18:47
  • Isn't there another way to do the check on type, other than using "is"? – Seb Nilsson Sep 16 '08 at 19:22
  • the final line should read `return (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == genericType) || IsAssignableToGenericType(baseType, genericType);` You can't avoid recursion just because the base class is generic. – Ben Voigt Sep 22 '10 at 03:53
  • @Ben: You’re right, of course, thanks a lot. Luckily, at least some people really read code attentively. ;-) – I’ve omitted the parentheses since `&&` binds stronger than `||` anyway. But please re-check if you’ve got time, I haven’t yet reached peak caffeine level. – Konrad Rudolph Sep 22 '10 at 08:17
  • @Joel You are right, thanks for pointing this out: my answer is buggy as outlined in your previous comment and James’ answer seems to work. Unfortunately his answer is buried deep below and only the OP can changed the accepted answer. But I’ll update my answer to reflect this. – Konrad Rudolph Nov 30 '12 at 09:09
5

A word of warning about generic types and using IsAssignableFrom()...

Say you have the following:

public class MyListBase<T> : IEnumerable<T> where T : ItemBase
{
}

public class MyItem : ItemBase
{
}

public class MyDerivedList : MyListBase<MyItem>
{
}

Calling IsAssignableFrom on the base list type or on the derived list type will return false, yet clearly MyDerivedList inherits MyListBase<T>. (A quick note for Jeff, generics absolutely must be wrapped in a code block or tildes to get the <T>, otherwise it's omitted. Is this intended?) The problem stems from the fact that MyListBase<MyItem> is treated as an entirely different type than MyListBase<T>. The following article could explain this a little better. http://mikehadlow.blogspot.com/2006/08/reflecting-generics.html

Instead, try the following recursive function:

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

/EDIT: Konrad's new post which takes the generic recursion into account as well as interfaces is spot on. Very nice work. :)

/EDIT2: If a check is made on whether genericType is an interface, performance benefits could be realized. The check can be an if block around the current interface code, but if you're interested in using .NET 3.5, a friend of mine offers the following:

    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);
    }
  • Re note to Jeff: This is by design because Markdown allows inline HTML. Thanks for your notice, I'll integrate it in my code. – Konrad Rudolph Sep 16 '08 at 18:53
  • Rich, I actually prefer your code to mine but unfortunately, it doesn't work if `genericType` is an interface type as in question. This can be easily fixed, though. – Konrad Rudolph Sep 16 '08 at 19:05
4
if (typeof(IEnumerable).IsAssignableFrom(typeof(V))) {
Jorge Ferreira
  • 96,051
  • 25
  • 122
  • 132
  • That uses reflection. It works...but I was wondering if you can do it without reflection, especially how you say specify an unspecialized generic type in c#. Thanks! – Chris Bilson Sep 16 '08 at 17:17
  • I'm not so sure. I think the runtime has enough information in the type system to be able to answer the IsAssignableFrom question without reflecting the assembly. – Isak Savo Sep 16 '08 at 17:27
4

Thanks for the great info. For convienience, I've refactored this into an extension method and reduced it to a single statement.

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

Now it can be easily called with:

sometype.IsAssignableToGenericType(typeof(MyGenericType<>))

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
1

I'd use overloading:

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
  • 4,635
  • 4
  • 32
  • 31
0

I used to think such a situation may be solvable in a way similar to @Thomas Danecker's solution, but adding another template argument:

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

But I noticed now that it does't work unless I specify the template arguments of the first method explicitly. This is clearly not customized per each item in the dictionary, but it may be a kind of poor-man's solution.

I would be very thankful if someone could point out anything incorrect I might have done here.

Community
  • 1
  • 1
Hosam Aly
  • 41,555
  • 36
  • 141
  • 182
0

I'm not sure I understand what you mean here. Do you want to know if the object is of any generic type or do you want to test if it is a specific generic type? Or do you just want to know if is enumerable?

I don't think the first is possible. The second is definitely possible, just treat it as any other type. For the third, just test it against IEnumerable as you suggested.

Also, you cannot use the 'is' operator on types.

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

See this question about types for more details. Maybe it'll help you.

Community
  • 1
  • 1
Isak Savo
  • 34,957
  • 11
  • 60
  • 92
  • I want to know if V in my example implements IEnumerable of anything, and I don't care what. I should have said items[key] is IEnumerable> in my example. Thanks for catching this. – Chris Bilson Sep 16 '08 at 17:32
-1

You want to check out the Type.IsInstanceOfType method

Paul van Brenk
  • 7,450
  • 2
  • 33
  • 38