Search code examples
c#genericsreflection

GetGenericArguments Recursively on inherited class type in C#?


In C# I have

class Gen1<T1, T2> {}
class Gen2<T2>: Gen1<MyObject, T2> {}
class Ins: Gen2<MyInterface> {}

var ins = new Ins();
Type insType = ins.GetType();

How can I get MyObject and MyInterface from insType (with correct order?)

I tried:

List<Type> types = new List<Type>();

do
{
    types.AddRange(checkType.GetGenericArguments());
    if(checkType.IsGenericType)
    {
        Type genType = checkType.GetGenericTypeDefinition();

        types.AddRange(genType.GetGenericArguments());
    }
    checkType = checkType.BaseType;
} while (checkType != null);

This gives MyInterface and T2. It did find the Gen2<T2> type, but I have no idea how to get that MyObject when Gen2 inherent from Gen1, and how to make the order correct too.


Solution

  • What you want are the generic arguments to the base-of-base type Gen1<T1, T2>. To get those, it will be convenient to introduce the following extension method:

    public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
    {
        while (type != null)
        {
            yield return type;
            type = type.BaseType!;
        }
    }
    

    And now you will be able to do:

    var types = insType.BaseTypesAndSelf()
        .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Gen1<,>))
        .Select(t => t.GetGenericArguments())
        .FirstOrDefault();
    

    Resulting in [MyObject,MyInterface].

    Or if you want the generic arguments to the topmost generic base type whatever it is, you could introduce the following additional extension method:

    // Returns the generic arguments of the topmost base type which is generic, or an empty array if there is none.
    public static Type [] GetTopmostBaseTypeGenericArguments(this Type type) =>
        type.BaseTypesAndSelf()
            .Where(t => t.IsGenericType)
            .Reverse()
            .Select(t => t.GetGenericArguments())
            .FirstOrDefault() ?? Array.Empty<Type>();
    

    And then do

    var types = insType.GetTopmostBaseTypeGenericArguments();
    

    Or if you have e.g.

    class MyKeyObjectDictionary<TKey> : Dictionary<TKey, MyObject> where TKey : notnull;
    class MyStringObjectDictionary : MyKeyObjectDictionary<string>;
    

    Then

    var types = typeof(MyStringObjectDictionary).GetTopmostBaseTypeGenericArguments(); 
    

    will return [System.String,MyObject].

    Demo here.