24

What is the difference between Type.IsGenericType and Type.IsGenericTypeDefinition ? Interestingly enough, MSDN's link for IsGenericTypeDefinition is broken.

Update: IsGenericTypeDefinition MSDN's entry

After playing a bit with trying to retrieve all the DbSets defined in a given DbContext, I was lead to the following, which behavior I am trying to understand: filtering properties via IsGenericType returns the desired results, while with IsGenericTypeDefinition not (does not return any).

It's interesting that from this post I have the impression that the author did get his DbSets using IsGenericTypeDefinition, while I did not.

Follows a sample that illustrates the discussion:

private static void Main(string[] args)
{
    A a = new A();
    int propertyCount = a.GetType().GetProperties().Where(p => p.PropertyType.IsGenericType).Count();
    int propertyCount2 = a.GetType().GetProperties().Where(p => p.PropertyType.IsGenericTypeDefinition).Count();

    Console.WriteLine("count1: {0}  count2: {1}", propertyCount, propertyCount2);
}

// Output: count1: 1  count2: 0

public class A
{
    public string aaa { get; set; }
    public List<int> myList { get; set; }
}
Veverke
  • 9,208
  • 4
  • 51
  • 95

2 Answers2

45

IsGenericType tells you that this instance of System.Type represents a generic type with all its type parameters specified. For example, List<int> is a generic type.

IsGenericTypeDefinition, on the other hand, tells you that this instance of System.Type represents a definition from which generic types can be constructed by supplying type arguments for its type parameters. For example, List<> is a generic type definition.

You can get a generic type definition of a generic type by calling GetGenericTypeDefinition:

var listInt = typeof(List<int>);
var typeDef = listInt.GetGenericTypeDefinition(); // gives typeof(List<>)

You can make a generic type from a generic type definition by providing it with type arguments to MakeGenericType:

var listDef = typeof(List<>);
var listStr = listDef.MakeGenericType(typeof(string));
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Don't you agree it does not make sense that [here](http://stackoverflow.com/questions/6304702/cast-propertyinfo-to-generic-type) the author does get DbSet instances using IsGenericTypeDefinition ? Based on your answer (and some testing from my side), you will not get DbSet returned as properties when doing `GetProperties().Where(p => p.IsGenericTypeDefinition)` – Veverke Aug 02 '15 at 15:25
  • @Veverke You are absolutely right, the author of the answer copy-pasted OP's code with an error. I made an edit to that answer, thank you very much! – Sergey Kalinichenko Aug 02 '15 at 16:38
  • 5
    In other words, a type for which IsGenericType returns true is a "real/complete/usable" generic type. A type for which IsGenericTypeDefinition is true is not really usable in code yet, it is a generic type "blueprint/container". – Veverke Aug 04 '15 at 12:41
  • @Veverke Yes, that's it. – Sergey Kalinichenko Aug 04 '15 at 12:46
  • Somehow related (or at least... the reason why I ended up asking this): [Getting the DbSet<>s of a DbContext](http://stackoverflow.com/questions/31771628/getting-a-dbcontext-dbsets-using-reflection?noredirect=1#comment51543068_31771628) – Veverke Aug 04 '15 at 12:58
  • Not interested in volunteering to [answer it](http://stackoverflow.com/questions/31771628/getting-the-dbcontext-dbsets-using-reflection-and-having-a-cast-to-the-proper-ge) ? :) – Veverke Aug 04 '15 at 13:17
  • A good answer, but this part is incorrect (emphasis mine): "`IsGenericType` tells you that this instance of `System.Type` represents a generic type **with all its type parameters specified**" - In actuality, the `Type.IsGenericType` property is `true` for any `Type` that refers to **any** type with generic type parameters, regardless of whether or not generic type arguments are supplied; for example the `Type` objects from `typeof(List<>)` and `typeof(Dictionary<,>)` (i.e. without specifying any type arguments) will both have `.IsGenericType == true`. – Dai Nov 25 '21 at 13:46
3

(This answer compares all of the generic-type-related properties of Type in a side-by-side table below, so if you're already familiar with .NET's generics and just want a reference then just scroll down to the table)


First, remember the difference between parameters and arguments, especially w.r.t. generic type parameters and generic type arguments (and also generic method type parameters and generic method type arguments):

  • A generic type parameter is the declared type "placeholder" in an "open" generic type.
    • For example, in class Generic<T0,T1> {}, the T0 and T1 symbols are the generic type parameters. Note that when simply given a generic class definition that's unused then there's no type arguments.
  • A generic type argument is the type-identifier specified for a generic type parameter by a consumer of a generic class.
    • For example, in Generic<String,Object> gen = new Generic<String,Object> then...
      • ...the generic type argument for generic type parameter T0 is String.
      • ...the generic type argument for generic type parameter T1 is Object.
    • However, generic type arguments don't need to be concrete types: they can be a generic type parameter from the consumer's context.
      • For example, in class Generic<TItem> { public Object Foo() { return new List<TItem>(); } }
        • ...then (inside the Foo method) the class Generic<TItem>'s generic type parameter TItem is used as the generic type argument for List<T>'s generic type parameter T.
  • Yes, if you get confused by all that don't worry because that's normal.
  • Finally, generic method type parameters and generic method type arguments work the same way as generic type parameters and generic type arguments, respectively, except they're scoped to a single method:
    • For example, the class NotGenericClass in class NotGenericClass { void GenericMethod<T>() { } } does not have any generic type parameters, but its method GenericMethod<T>() does have a single generic method type parameter - and if GenericMethod is never ever actually called/used/invoked then GenericMethod will not have any generic method type arguments as those only exist at generic instantiation sites (i.e. at the point of generic instantiation).

Given these C# classes...

class NormalClass { }

class Generic<T> { }

class Derived : Generic<String> { }

class HasGenericMethod { public void Foo<T>() {} }

...and these Type instances from GetGenericArguments():

Type[] genericTypeArgs = typeof(Generic<>).GetGenericArguments();
Type genTypeArg = genericTypeArgs.Single();

Type[] genericMethodTypeArgs = typeof(HasGenericMethod).GetMethod( nameof(HasGenericMethod.Foo) ).GetGenericArguments();
Type genMethodArg = genericMethodTypeArgs.Single();

...then their typeof() expressions will have these properties:

Example typeof(NormalClass) typeof(Generic<>) typeof(Generic<String>) typeof(Derived) genTypeArg genMethodArg typeof(Generic<String>[])
Type properties
Type.IsTypeDefinition Yes Yes No Yes No No No
Type.IsGenericType No Yes Yes No No No No5
Type.ContainsGenericParameters No Yes No No Yes4 Yes4 No
Type.GenericTypeArguments Empty Empty { typeof(String) } Empty Empty Empty Empty
Type.IsConstructedGenericType No No Yes No No No No
Type.IsGenericTypeDefinition No Yes No No No No No
Generic parameter properties:
Type.IsGenericParameter No No No No Yes Yes No
Type.IsGenericMethodParameter No No No No No Yes No
Type.IsGenericTypeParameter No No No No Yes No No
Methods:
Type.GetGenericArguments() Empty { typeof(T) } { typeof(String) } Empty Empty Empty { typeof(String) }
Type.GetGenericParameterConstraints() Exception1 Exception1 Exception1 Exception1 Empty Empty Exception1
Type.GetGenericTypeDefinition() Exception2 typeof(Generic<>) typeof(Generic<>) Exception2 Exception2 Exception2 Exception2

You can generate this table yourself (albiet, transposed) using this LinqPad script.


As a reminder to myself: if you have a Type object from Object.GetType() for an object that may be either a closed generic type (i.e. Object.GetType().IsConstructedGenericType == true), or is a non-generic type derived from that generic type, and you want to find out what, do this:

private static readonly Type _knownGenericType = typeof(Generic<>);

public static Boolean TryGetTypeArgsOfKnownGenericType( Object obj, [NotNullWhen(true)] out Type? actualArgType )
{
    Type t = obj.GetType();
    while( t != null )
    {
        if t.IsConstructedGenericType && t.GetGenericTypeDefinition() == _knownGenericType )
        {
            Type[] tArgs = t.GetGenericArguments();
            actualArgType = tArgs.Single();
            return true;
        }

        t = t.BaseType;
    }

    actualArgType = null;
    return false;
}

So this code below will print "Sucess: T := System.String" twice:

if( TryGetTypeArgsOfKnownGenericType( new Derived(), out Type? tArg ) )
{
    Console.WriteLine("Success: T := " + tArg.FullName);
}

if( TryGetTypeArgsOfKnownGenericType( new Generic<String>(), out Type? tArg ) )
{
    Console.WriteLine("Success: T := " + tArg.FullName);
}

Footnotes:

  1. InvalidOperationException: "Method may only be called on a Type for which Type.IsGenericParameter is true."

  2. InvalidOperationException: "This operation is only valid on generic types."

  3. typeof(T) is typeof(Generic<>).GetGenericArguments().Single()

  4. It's surprising that typeof(T).ContainsGenericParameters == true when T is a generic type parameter without an argument set (i.e. T is undefined), so I'd have expected an InvalidOperationException to be thrown instead.

    • The documentation for ContainsGenericParameters seemingly justifies returning true (emphasis mine):

      For convenience and to reduce the chance of error, the ContainsGenericParameters property provides a standard way to distinguish between closed constructed types, which can be instantiated, and open constructed types, which cannot. If the ContainsGenericParameters property returns true, the type cannot be instantiated.

  5. Apparently using typeof(T[]) when T is a constructed generic type: the ContainsGenericParameters property is false but the GetGenericArguments() method returns a non-empty array of the type-arguments of T instead of the type-arguments of System.Array (which isn't actually a generic type).

    • For example:
      typeof(Generic<String>[]).IsGenericType         == false
      typeof(Generic<String>[]).GetGenericArguments() == new[] { typeof(String) }
      
    • This is documented in the rightmost column of the above table.
    • And described in the documentation:

      The ContainsGenericParameters property searches recursively for type parameters. For example, it returns true for an array whose elements are type A<T> even though the array is not itself generic. Contrast this with the behavior of the IsGenericType property, which returns false for arrays.


Dai
  • 141,631
  • 28
  • 261
  • 374
  • *Because now we have support for tables in StackOverflow's Markdown:* ? – Veverke Nov 25 '21 at 14:46
  • 1
    @Veverke StackOverflow added support for **real tables** in our posts at the end of 2020 ( https://meta.stackexchange.com/questions/356997/new-feature-table-support ) - so I decided to add this answer to this question that adds a table that compares the `Type` property values as that should be easier to see and compare side-by-side instead of having to read prose text or monospaced tables. – Dai Nov 25 '21 at 15:05
  • Oh now I see, great, thanks! – Veverke Nov 25 '21 at 18:56