11

I need to test if a value is an instance of a generic base class, without knowing the generic type parameter. Using the MSDN example as the base of my example, this is what I'd like to accomplish:

using System;

public class Class1<T> { }
public class DerivedC1 : Class1<int> { }

class IsSubclassTest
{
   public static void Main()
   {
      Console.WriteLine(
          "DerivedC1 subclass of Class1: {0}",
          typeof(DerivedC1).IsSubclassOf(typeof(Class1<>)) // <- Here.
      );
   }
}

While this is syntactically correct, it always yields false. If I remove the generic type parameter, it works as expected (returns true).

How can I test if a class type is a subclass of a generic base class, without knowing its generic type parameter as such?

John Weisz
  • 30,137
  • 13
  • 89
  • 132
  • Similar thread: https://stackoverflow.com/questions/18687264/check-if-object-is-of-non-specific-generic-type-in-c-sharp – nawfal Aug 03 '20 at 04:26

3 Answers3

22

The problem is that DrevidedC1 is not a sublcass of Class1<T>, it's a subclass of Class1<int>. Make sure you understand this subtle diference; Class1<T> is a open type (T can be anything, it hasn't been set) while DerivedC1 extends a closed type Class1<int> (it's not open in T anymore, T is set to int and only int). So when you do the following:

 typeof(DerivedC1).IsSubclassOf(typeof(Class1<>))

The answer is evidently false.

What you need to do is check if the generic type definition of DerivedC1's base type (think of it as the corresponding open generic type of Class1<int>) equals Class1<T> which it clearly does.

The correct code is therefore:

typeof(DerivedC1).BaseType.GetGenericTypeDefinition() == typeof(Class1<>));

Or better yet, as Matías Fidemraizer states in his answer:

typeof(DerivedC1).BaseType.GetGenericTypeDefinition().IsAssignableFrom(typeof(Class1<>)));
Community
  • 1
  • 1
InBetween
  • 32,319
  • 3
  • 50
  • 90
  • @JohnWhite Unless your checks may be implemented using reflection and types providing full type names, since you're strongly-typing your checks, there should be no issue with checking types from different assemblies. – Matías Fidemraizer May 12 '16 at 10:59
6

Changing typeof(DerivedC1).IsSubclassOf(typeof(Class1<>)) to typeof(Class1<>).IsAssignableFrom(typeof(DerivedC1).BaseType.GetGenericTypeDefinition()) should be enough in your case.

Type.IsAssignableFrom is more powerful than using Type.IsSubClassOf because it just checks if some type is assignable to other type. This includes, the same type, interface types and other cases.

Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • What do you mean by _"should be enough in your case"_? I'm struggling to get this to work. Could it be that it's not adequate when the generic type argument is a type from another assembly? – John Weisz May 12 '16 at 10:51
  • @JohnWhite No, I mean that if you're using it in a single place, you shouldn't need to generalize the solution. You can use it *as is* ;P – Matías Fidemraizer May 12 '16 at 10:55
  • @JohnWhite I would go with the `IsAssignableFrom` route, it also works to check interface types. – Matías Fidemraizer May 12 '16 at 10:57
  • I'm certain there is something I'm missing. I copied the exact code you provided, ran regex matching after changing names to completely ensure I did everything right, but it just wouldn't return `true`. I have no idea what's afoot. Obviously, the code snippet in the question is a [mcve]. – John Weisz May 12 '16 at 10:59
  • @JohnWhite Uhm, check this fiddle: https://dotnetfiddle.net/toLtnT Here I provide you a working sample – Matías Fidemraizer May 12 '16 at 11:00
  • I'm not usre the first method is correct. `typeof(DerivedC1).BaseType.GetGenericTypeDefinition()` is *not* a sublcass of `Class1<>`, its `Class1<>` itself. The second method should work perfectly. – InBetween May 12 '16 at 11:01
  • Wow. Man. I had the derived type and the base type swapped. Jesus Christ. For some reason I thought the derived should come first in the condition and didn't bother to check. – John Weisz May 12 '16 at 11:02
  • @InBetween But using `IsAssignableFrom` should work in either case – Matías Fidemraizer May 12 '16 at 11:02
  • @MatíasFidemraizer Very true. I'd edit your answer though, as it stands now, the first option will return `false`. – InBetween May 12 '16 at 11:04
5

There's special methods on Type for this sort of thing. As far as I can see, you'll need to walk up your base-types and check each in turn until you either (a) hit a match or (b) get to the top of the inheritance hierarchy (i.e. System.Object).

As such, the following (recursive) extension method:

public static class TypeExtensions
{
    public static bool IsDerivedFromGenericParent(this Type type, Type parentType)
    {
        if(!parentType.IsGenericType)
        {
            throw new ArgumentException("type must be generic", "parentType");
        }
        if(type == null || type == typeof(object))
        {
            return false;
        }
        if(type.IsGenericType && type.GetGenericTypeDefinition() == parentType)
        {
            return true;
        }
        return type.BaseType.IsDerivedFromGenericParent(parentType)
            || type.GetInterfaces().Any(t=>t.IsDerivedFromGenericParent(parentType));
    }
}

will allow you to do the following

typeof(DerivedC1).IsDerivedFromGenericParent(typeof(Class1<>))

...and will also work if you test something derived from DerivedC1.

spender
  • 117,338
  • 33
  • 229
  • 351
  • @JohnWhite Could it be that you are testing for an interface implementation? I adjusted the code above to allow for this. Seems to work fine for a class derived from `List<>` and testing if it is `IEnumerable<>` both of which are definitely in a different assembly. – spender May 12 '16 at 11:08