Reflection and Generics

I spent some time last week working on a tool we use internally to print out the C# “header file” view of assemblies. We use it as part of the WinFX API reviews to give an overview of that the API looks like. I am still in the middle of updating, but I do plan to post it when it is a bit more stable.

The main thing I did was to add generic support. This was pretty interesting as doing it gave me a really good idea of how the CLR and compilers actually work to create generic type.

Consider if I have an assembly with the following two types.

public class List<T> {

}

public class SortedList<T> where T : IComparable {

}

A quick ILDASM of the assembly shows me what the metadata really looks like:

.class public auto ansi beforefieldinit List<([mscorlib]System.Object) T>

       extends [mscorlib]System.Object

.class public auto ansi beforefieldinit SortedList<([mscorlib]System.IComparable) T>

       extends [mscorlib]System.Object

Notice the “type” of the type parameter, T? This is how we include the constraints.. List can work over any type (all types both value and reference satisfy the constraint of being derived from System.Object. Whereas sorted list will only work over types that implement the IComparable interface.

Cool enough, but how do we get reflection to give us this data… Once you understand the metadata layout it is not that bad… See comments in line

      void WriteTypeConstraints(Type type)

      {

            //Fist we loop through all the generic arguments (T) in our case

            foreach (Type t in type.GetGenericArguments())

            {

                  //Then we find out what interfaces each of them implements...

                  Type[] arr = t.GetInterfaces();

                 

                  //And what custom attributes it has (for the new constraint)

                  object[] arr2 = t.GetCustomAttributes(true);

                  //If there are any or there is a base type other than object then we

                  //have some constraints and therefore need to write out

                  //the where clause.

                  if (t.BaseType != typeof(object) || arr.Length+arr2.Length > 0)

                  {

                        Write(" where ");

                        WriteTypeName(t);

                        Write(":");

                  }

                  //if there is a base type other than object, it counts as a

                  //constraint and needs to be written out

                  if (t.BaseType != typeof(Object))

                  {

                        WriteTypeName(t.BaseType);

                        //Find out if we need to write more out not..

                        if (arr.Length + arr2.Length > 0) Write(",");

                  }

                  //Here we write all the constraints for the interfaces

                  for (int i = 0; i < arr.Length; i++ )

                  {

                        WriteTypeName(arr[i]);

                        if (i < arr.Length-1 || arr2.Length>0) Write(",");

                  }

                  //And here for the custom attributes

                  for (int i = 0; i < arr2.Length; i++)

                  {

                        //There is only one we use today, and that is for the

                        //"new" constraint.

                        if (arr2[i].GetType() ==

                        typeof(System.Runtime.CompilerServices.NewConstraintAttribute))

                  {

                              Write("new()");

                        }

                        else

                        {

                              Write(arr2[i].ToString());

                        }

                        if (i < arr.Length - 1) Write(",");

                  }

            }

      }

 

 

Fairly simple, and it gives up something pretty in the end:

 

public class List<T>

{

      public List();

}

public class SortedList<T> where T : IComparable

{

      public SortedList();

}

What do you think?  Have you used reflection and generics?  What could be easier?