Share via


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?

Comments

  • Anonymous
    January 27, 2004
    The comment has been removed
  • Anonymous
    January 27, 2004
    Oh, and it looks like generic parameters are now referred to as generic arguments...is that a post-PDC change?
  • Anonymous
    January 27, 2004
    Does your code also work on types which have a constructor constraint?
  • Anonymous
    January 27, 2004
    Rich -- Yes, this code supports the new constraint...
    typeof(System.Runtime.CompilerServices.NewConstraintAttribute))

    {

    Write("new()");

    }

  • Anonymous
    May 24, 2004
    Please help me with the questions below :
  • Anonymous
    June 21, 2004
    Brad,

    Can you help me out with this problem? I've got some code that is dynamically compiled and loaded into an Assembly type. In the past, I could use myAssembly.CreateInstance to get an instance of a type in the Assembly. But what if the type I need to create requires a type argument? Is there any way to pass that through reflection to get an instance of the type?

    Thanks.
  • Anonymous
    June 21, 2004
    I ended up using BindGenericParameters:

    System.Type constructedType, genericType;
    genericType = compiled.CompiledAssembly.GetType(dynamicTypeName + "1&quot;);<br>constructedType = genericType.BindGenericParameters(new Type[1] {typeof(T)});<br>comparer = constructedType.GetConstructor(new Type[0] {}).Invoke(null)<br> as System.Collections.Generic.IComparer&lt;T&gt;;<br><br>I am wondering why I have to add the &quot;1" at the end of my type name to get the generic. I can see that it indicates the number of type parameters, but the example for BindGenericParameters in the docs didn't, IIRC, add that (so its code shouldn't work). Are you guys going to work around this or is this the format you're sticking with?

  • Anonymous
    June 21, 2004
    The comment has been removed
  • Anonymous
    June 21, 2004
    Thanks, Joel. I suppose that once you're aware of it, it's not a big deal, but it did throw me for a loop, especially since the BindGenericParameters example in the current docs didn't have that. I had to go into debug to figure out what the type name actually was.

    It would be nice to have it do some sort of search if no arity is indicated, e.g.:
    1. Look for a non-generic match first.
    2. Look for generic match, going from 1-n arity, matching the first found.

    Granted, this might cause more confusion than it's worth, but as long as this or the current format is documented, we'll probably be okay.

    Just curious, do you find or expect much overloading on arity?

  • Anonymous
    June 16, 2009
    PingBack from http://workfromhomecareer.info/story.php?id=34380