C# Language Determining generic arguments of instances of generic types


Example

If you have an instance of a generic type but for some reason don't know the specific type, you might want to determine the generic arguments that were used to create this instance.

Let's say someone created an instance of List<T> like that and passes it to a method:

var myList = new List<int>();
ShowGenericArguments(myList);

where ShowGenericArguments has this signature:

public void ShowGenericArguments(object o)

so at compile time you don't have any idea what generic arguments have been used to create o. Reflection provides a lot of methods to inspect generic types. At first, we can determine if the type of o is a generic type at all:

public void ShowGenericArguments(object o)
{
    if (o == null) return;

    Type t = o.GetType();
    if (!t.IsGenericType) return;
    ...

Type.IsGenericType returns true if the type is a generic type and false if not.

But this is not all we want to know. List<> itself is a generic type, too. But we only want to examine instances of specific constructed generic types. A constructed generic type is for example a List<int> that has a specific type argument for all its generic parameters.

The Type class provides two more properties, IsConstructedGenericType and IsGenericTypeDefinition, to distinguish these constructed generic types from generic type definitions:

typeof(List<>).IsGenericType // true
typeof(List<>).IsGenericTypeDefinition // true
typeof(List<>).IsConstructedGenericType// false

typeof(List<int>).IsGenericType // true
typeof(List<int>).IsGenericTypeDefinition // false
typeof(List<int>).IsConstructedGenericType// true

To enumerate the generic arguments of an instance, we can use the GetGenericArguments() method that returns an Type array containing the generic type arguments:

public void ShowGenericArguments(object o)
{
    if (o == null) return;   
    Type t = o.GetType();
    if (!t.IsConstructedGenericType) return;

    foreach(Type genericTypeArgument in t.GetGenericArguments())
        Console.WriteLine(genericTypeArgument.Name);
}

So the call from above (ShowGenericArguments(myList)) results in this output:

Int32