Comparing Different Versions of the CLR Using Reflection, Part 2: Namespaces and Generics

Mar 4, 07:43 pm

Introduction

This articles is based on the .NET 2.0 Beta, but the principals should work perfectly for the released version.

Even though .NET 2005 Beta 2 is supposed to be "feature complete" for the final version of the Microsoft .NET Framework, it is not easy to find all the changes and additions to the namespaces and types compared to the other shipped versions. In the first article of this series I showed how you can use reflection to dig into the CLR. I explained where assemblies are stored, how things changed in .NET 2005 and which method to use to get a System.Reflection.Assembly object corresponding to the assembly I want to dissect. This second article takes the time to look into the Assembly class to explore an assembly contents. The third and last article will cover the exploration of type members with special use of the unmanaged COM-based reflection API to decypher P/Invoke signatures.

Using the Assembly Class

The interesting implementation details that I'm going to cover of using the Assembly class to explore an assembly contents are these:

  • No direct access to namespaces . The reflection API does not provide a direct way to list the namespaces defined in an assembly. In the article I explain a workaround using types.
  • Exception handling when listing types . When you try to load types using reflection, you have to take care of certain possible exceptions. In this case you can use the unmanaged reflection COM-based API to work aruond the exceptions and get more information. I will take the time to detail what to do in order to get additional details such as enumerations and delegates description or type hierarchy parsing.
  • Generics and reflection . In .NET 2005, the new notion of generics is now supported within the CLR. I show you how the reflection API has been updated to take it into account.

System Requirements

To run the code for this sample you should have:

  • The .NET Framework versions 1.1, 2.0 (Beta 1 or Beta 2)
  • VS.NET 2003 and VS.NET 2005 Beta 2

Installing and Compiling the Sample Code

The C# sample download for this series contains several VS .NET solutions for C# and C++ projects. Note that the download for this article is identical to the downloads for the other two articles of the series.

  • LoadClrAssembly11 : A console application test to load mscorlib.dll and system.dll compiled with CLR v1.1
  • LoadClrAssembly : A console application test to load mscorlib.dll and system.dll compiled with CLR v2.0 beta 2
  • LoadedAssemblies : Sample code for v1.1\.NET 2005 beta 1 + beta 2 executables to see what assemblies are loaded. To compile on your machine, open a VS Command Prompt and type "csc /out:AssembliesXXX program.cs" where XXX is the version of the CLR you are using to compile.
  • CLRDump11 : A tool to dump assemblies compiled with CLR v1.1
  • CLRDumpBeta1 : A tool to dump assemblies compiled with CLR v2.0 beta 1
  • CLRDump : A tool to dump assemblies compiled with CLR v2.0 beta 2

Namespaces and Types

The first article in the series explained which folder to load assembly files from for a given version of the CLR, and how to use Assembly.LoadFrom() to get the corresponding Assembly instance that allows you to access its metadata. But what files should you choose in this folder? In the sample code I make the same choice as the Object Browser pane in Visual Studio 2005: mscorlib.dll - ie. all DLLs starting with "system". If you are interested in other assemblies such as Microsoft*.dll, feel free to change the code of the DumpCLR() method in CLRContent.cs .

Assembly Attributes

Once each assembly file is loaded using Assembly.LoadFrom() , the method DumpAssemblyTypes() is responsible for printing the name and version, using the Name and Version properties of the AssemblyName returned by Assembly.GetName() .

    private void DumpAssemblyAttributes(Assembly assembly)
    {
        // print them sorted 
        object[] attributes = assembly.GetCustomAttributes(false);


        if (attributes.Length != 0)
        {
            String[] sortedAttributes = new string[attributes.Length];
            for (int current = 0; current < attributes.Length; current++)
            {
                sortedAttributes[current] = 
                    FormatAssemblyAttribute(attributes[current]);
            }
            Array.Sort(
                sortedAttributes, 
                CaseInsensitiveComparer.DefaultInvariant
                );


            foreach (string attribute in sortedAttributes)
            {
                WriteLine(attribute);
            }
        }
    }

Assembly.GetCustomAttributes() is called to retrieve the list of attributes decorating the assembly. I'm using the integrated sorting feature of the Array class to print the attributes in alphabetical order. The FormatAssemblyAttribute() method is responsible for providing the details of each possible attribute for an assembly. I have listed them in the comments but I have not implemented the actual formatting. Note that an assembly attribute seems to disappear during compilation: In any C# project created by Visual Studio 2005, the AssemblyInfo.cs file lists the attributes attached to the generated assembly. There is a special treatment in the case of the AssemblyVersionAttribute . From its name, you'll have guessed that it is used to set the version of the assembly split into a major, minor, build and revision number. If you need to get this attribute, you won't be able to through Assembly.GetCustomAttributes() . Instead, you should get the assembly name using the Assembly.GetName() method and then ask for its Version property. This sealed class from System namespace exposes five read-only properties:

    public int Major { get; }
    public int Minor { get; }
    public int Revision { get; }
    public short MajorRevision { get; }
    public short MinorRevision { get; }


    public int Build { get; }

The two highlighted properties can return misleading values in some cases. First, MajorRevision always returns 0. Why? Because, Visual Studio 2005 does not allow you to set the revision number higher than 65534 and MajorRevision returns the high word of that value. Since the maximum value is lower than 65535, the 16 bits high part is 0.

For the MinorRevision , since a signed short has a numeric value between -32768 and 32767, depending on the Revision of the version of the assembly, the MinorRevision propery will return a negative value. Here are some examples so you can see how things begin to change from 32767:

RevisionMinorRevision
11
3276732767
32768-32768
65534-2

No Namespace-driven Reflection API

The next step should be to get the list of namespaces and then for each, enumerate the types defined within. Unfortunately, the Assembly type does not expose a GetNamespaces() method. It is only possible to get an array containing the public types using GetExportedTypes() or by means of an array of all the types (including private and internal types) using GetTypes() . A type is described by a Type instance which has a Namespace property which yields the namespace date you're interested in here.

I have defined two classes to help in navigating between assemblies, namespaces and types. First, AssemblyDescription is responsible for wrapping an Assembly reference, parsing all the types and storing a description of the namespaces inside which the types are defined. Then, an array of NamespaceDescription instances is returned by the AssemblyDescription.Namespaces property. Don't forget to call ParseAssembly() before (see DumptTypes() method in CLRContent.cs for an example). In order to help in filtering the types from a NamespaceDescription , several properties are available:

Property nameDescription and detection trick
SortedByCategoryTypesArray of types sorted, first interfaces, then delegates, classes, structures and enumerations
SortedByNameTypesArray of types sorted alphabetically by their name
InterfacesArray of interfaces only and sorted alphabetically by their name

Type.IsInterface property

DelegatesArray of delegates only and sorted alphabetically by their name

CurrentType.IsSubclassOf(Type.GetType("System.MulticastDelegate")

ClassesArray of reference types only and sorted alphabetically by their name

Type.IsClass property

StructuresArray of value types only and sorted alphabetically by their name

Type.IsValueType and !Type.IsEnum because an enumeration is a value type but I don't want to repeat the same type in two categories

EnumsArray of enumerations only and sorted alphabetically by their name

Type.IsEnum property

For the first two properties, I take advantage of the sorting features offered by the Array type in the .NET Framework. In all the source code, I define four types responsible for comparison, each one implementing the IComparer interface: AssemblyComparer for AssemblyDescription objects; NamespaceComparer for NamespaceDescription objects; TypeComparer for Type objects; and MemberComparer for MemberInfo objects.

Each one offers different criteria, for example, when you need to sort types by name, you simply have to call Array.Sort(ArrayOfTypes, new TypeComparer(TypeComparer.Sorts.Name)) and Array.Sort(ArrayOfTypes, new TypeComparer(TypeComparer.Sorts.Category)) to sort by category instead. Feel free to expand these IComparer inherited types with your own personal sorts as needed.

Dumping the Types

It is time to dig into the implementation of NamespaceDescription.ParseAssembly() . This method takes a boolean as parameter. If you pass true , you get all types, including private and internal, using with Assembly.GetTypes() . Otherwise, you only get the public types returned by Assembly.GetExportedTypes() . Note that these two methods return nested types. In the AssemblyDescription.ParseAssembly() method, the nested types are not kept in the type cache because I don't want to show them at the same level as non nested types. This way, when it is time to build the list of types under a namespace, you can recursively dump the nested types under each type using the Type.GetNestedTypes() method. When you want to know if a type is a nested into another type, you simply check whether its DeclaringType property is null or not.

When dealing with different versions of the CLR, you have to catch specific exceptions. In version 1.1, Assembly.LoadFile() seems to work well in loading the 1.0 version of mscorlib.dll or system.dll , but you encounter problems as soon as you try to list their types. For mscorlib , Assembly.GetTypes() and Assembly.GetExportedTypes() trigger a specific ReflectionTypeLoadException exception that is somewhat complicated to decipher.

In addition to the " One or more of the types in the assembly unable to load. " message, it also contains two properties which are supposed to help you guess where the problem comes from and what the loadable types are. The LoaderExceptions property returns an array listing one exception per unloadable type. The Types property returns an array, the first items of which contain the loadable types, the rest being filled with null values. For example, in the case of code compiled against CLR version 1.0 trying to load a version 1.1 of mscorlib , Types has 1448 elements with only the first 140 containing types and LoadExceptions contains 1307 times the same TypeLoadException with " Could not load type System.Object from assembly mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 because the parent does not exist. " as the message. Unfortunately, there is no way to know the missing 1307 "unreflectable" types in managed code.

Strangely enough, if you try to load any version of mscorlib with ILDASM, no problems are encountered. As I mentioned in part 1 of this series this is due to the fact that it is using the unmanaged COM-based reflection API. Let's do the same here. For the managed C++ ReflectHelper assembly I wrote, GetDefinedTypes() wraps the few unmanaged calls needed to get the name of each defined type. This method takes the name of an assembly as a parameter and returns the list of type names into an array of strings. The IMetaDataDispenser reflection root interface is requested to get the IMetaDataImport interface which describes an assembly. Its EnumTypeDefs() method fills an array with a token per type. The name of this token type is returned by the GetTypeDefProps() method (see ReflectHelper.cpp for the implementation). For more details about the unmanaged reflection API refer the Related Links section for and read Matt Pietrek's article and study the documentation in the Tool Developers Guide\docs folder of the .NET Framework SDK installation. Note that this documentation is not installed with Visual Studio 2005 Beta 2.

So, if a ReflectionTypeLoadException is caught, the type names are computed through ReflectHelper. In a second pass, the loaded types can be detected via ReflectionTypeLoadException . Types are cached and the missing types are only stored by name in the _unreflectedTypes StringCollection . At that point, there is no way to go beyond type name comparison for this kind of assembly using the managed side of reflection.

Details of Types

Once a the Type instance corresponding to an assembly type has been obtained using Assembly.GetType() or Assembly.GetExportedTypes() , it is time to dump the meaningful description. I also sort them into five C# categories: enumeration, delegate, interface, class (reference type) and structure (value type).

Dumping an Enumeration

An enumeration is detected if the Type.IsEnum property returns true . Beware that Type.IsValueType() also returns true for an enumeration. It is possible to tell the difference between a Flag and an Enum by detecting the attachment of the Flags attribute using GetCustomAttributes() passing Type.GetType("System.FlagsAttribute") as a parameter. The AddEnumMembers() method is responsible for listing the different names and boxed values of the given enumeration type using Enum.GetNames() and Enum.GetValues() .

An enumeration value can be signed or unsigned, depending on the underlying type used to store it. You should not rely on Type.GetUnderlyingType() which returns the type itself, but rather on Enum.GetUnderlyingType() , passing the reflection type corresponding to the enumeration as a parameter. This is important because it is better to display high values either as negative or as large unsigned number, according to their underlying type ( System.Int32 for the former and System.UInt32 for the latter). To get long numbers displayed with a thousands separator, the formatting to use is "#,##0" . In case of unsigned enumerations, it is possible that values are too high for a signed integer and since C# is strongly typed, Convert.ToInt32() will throw an OverflowException (most probably because of a check during unboxing, rather than because of the conversion itself). This is why you need to take care of the underlying enumeration type to call the right Convert() method: ToIn32() or ToUInt32() (see GetEnumValue() for implementation details). Once you get an unboxed value, it is easy to get a signed integer from an unsigned integer simply with an (int) explicit cast.

I have noticed that flag values are more easily deciphered in hexadecimal. This is why the enumeration values are displayed in decimal in the following two cases: if the last value of the enumeration is the count of elements minus 1 (corresponding to a [0..count-1] enumeration), or if it is not a Flag.

Dumping a Delegate

A delegate is a type derived from MulticastDelegate , so passing Type.GetType("System.MulticastDelegate") to IsSubclassOf() should return true for a delegate. The interesting part of a delegate is the prototype of the matching methods. Each delegate type defines an Invoke() method that has the right prototype. Therefore, the corresponding MethodInfo is easily retrieved calling GetMethod("Invoke") . I will detail how to compute a method signature from its MethodInfo in the third article of this series, in the section dedicated to the different members of a type. Anyway, feel free to jump to the GetMethodSignature() implementation (see ReflectionHelper.cs in the sample code) if you can't wait.

Dumping Interfaces, and Value and Reference Types

For the three remaining possible kind of types, Type.IsInterface detects interfaces, Type.IsValueType does the same for C# structures and Type.IsClass returns true for classes. The construction of the corresponding description text is the same for these three.

When you take a look at a non sealed type, you may be interested in the types it derives from and in the interfaces it implements. This is the purpose of the FormatTypeParent() helper method. Given a type, the method gets its single base type using the Type.BaseType property. Since all types derive from the System.Object or System.ValueType types, I choose to hide this particular inheritance which would generate too much useless information.

The .NET Framework allows a single parent class for a type (i.e. forbids C++-like multiple inheritance), but several interfaces may be implemented. When you call GetInterfaces() on a type, you get the interfaces implemented by this type, but also those implemented by its parent. The purpose of the FormatTypeParent() method is to clearly separate the interfaces implemented by the type itself from those implemented by its parent with the following format: ParentClass [Iaaa,...,Iuuu], Ixxx,..., Izzz .

static public string FormatTypeParent(Type type)
{ 
    // sanity checks
    if (type == null)
        throw new ArgumentNullException("type");
    StringBuilder text = new StringBuilder();
    string parentInterfaces = null;
    string interfaces = null;
    // start with the base class
    Type ParentType = type.BaseType;
    if (ParentType != null)
    {
        // get the base class name if different from 
        // "system.object" or "ValueType"
        if (
            (ParentType != Type.GetType("System.Object", false, true)) &&
            (ParentType != Type.GetType("System.ValueType", false, true))
            )
        {
            text.Append(GetTypeName(ParentType)); 


            // get the base class interfaces 
            parentInterfaces = 
                FormatImplementedInterfaces(ParentType, null);


            if (parentInterfaces != null)
            {
                text.AppendFormat(" [{0}]", parentInterfaces);
            }
        }
    }
    // get the interfaces it implements
    interfaces = FormatImplementedInterfaces(type, ParentType);


    if (interfaces != null)
    {
        // add ',' only if there is a base type
        if (text.Length != 0)
            text.Append(", ");
        text.Append(interfaces);
    }
    return(text.ToString());
}

The implementation is straightforward, I'm using the BaseType property of the given type to get the description of the type it derives from. Then, the FormatImplementedInterfaces() helper is called with both a given type and its parent to compute the "split" inheritance. The algorithm is simple, the interfaces of the type and its parent's (if any) are retrieved using GetInterfaces() and stored in two arrays. Then, for each interface in the former but not in the latter, this means that it is implemented at the type level but not by its parent. This is the InterfaceExists() helper that takes a type and an array of types, and returns true if it finds the former within the latter.

static private string FormatImplementedInterfaces(Type type, Type 
   parentType)
{
    // get all interfaces
    Type[] Interfaces = type.GetInterfaces();


    if (Interfaces.Length == 0)
        return(null);
    // get interfaces already implemented by the parent type if any
    Type [] parentInterfaces = null;
    if (parentType != null)
        parentInterfaces = parentType.GetInterfaces();


    // don't list interfaces implemented by its parent
    StringBuilder text = new StringBuilder();
    for (int Current = 0; Current < Interfaces.Length; Current++)
    {
        // first, check if this interface is not already 
        // implemented by its parent
        if (parentType != null)
        {
            if (InterfaceExists(Interfaces[Current], parentInterfaces))


                continue;
        }
        // implemented by the given type
        text.Append(GetTypeName(Interfaces[Current]));
        // add a ',' for every interface except the last one
        if (Current != Interfaces.Length-1)
            text.Append(", ");
    }
    return(text.ToString());
}

For example, here is what you get for Type :

   public class Type : MemberInfo [ICustomAttributeProvider, _MemberInfo],
      _Type, IReflect

which means that Type implements _Type and IReflect interfaces and inherits from MemberInfo , itself implementing ICustomAttributeProvider and _MemberInfo interfaces. This is a very simple way to visualize what is implemented by a type and what it brought by its parents.

Generics and Reflection

One of the most anticipated features of version 2.0 of the CLR, is generics. Generics are very similar to the templates that C++ developers have been used to working with for years. In C# they are defined using the same bracketed <> syntax as C++ templates, but in contrast to templates, generics also provide additional type safety with the notion of "constraints". I will simply scratch the surface here, but for a complete and deep coverage of generics, you should read the article written by Juval Lowy on MSDN web site found in the Related Links to this article. I will also explain how .NET reflection has been updated to take care of generic types and methods.

Notions of Generics

You can define a generic type with almost the same syntax as in C++. For example, the following lines define an "unbound" generic type, meaning that the generic T type parameter is not bound to any particular type:

   class Simple<T>
   {
   }

When you declare a variable with a certain type such as int , the resulting type is a "bound" type:

   Simple<int> si = new Simple<int>();

The Type returned by reflection from si through its GetType() method allows us to easily retrieve details for both the bound and unbound associated types. From the previous definition, guess what the following code generates as output:

    Type currentType = si.GetType();
    Console.WriteLine(
        currentType.Name + "\n" + 
        currentType.ToString()


        );

The answer is this:

Simple'1
Simple'1[System.Int32]

The Name property returns the name of the type without the generic parts and the ToString() method returns a string that is not really compliant with the C# syntax. However, both have text of the form 'n at the end where n seems to stand for the number of generic parameters. This is important if you need to get the Type instance reflecting a generic type through the Assembly.GetType() method for example, you have to build the right "signature", including the ‘'' character.

From a bound type, you can get its generic definition type through the Type.GetGenericTypeDefinition() method.

The Type class provides several properties that help determine the difference between a bound and an unbound generic type.

Type PropertyBound / Simple<int>Unbound / Simple<T>
IsGenericTypeTrueTrue
HasGenericArgumentsTrueTrue
IsGenericTypeDefinitionFalseTrue
ContainsGenericParametersFalseTrue

As expected, the IsGenericTypeDefinition is the property to use in order to make the difference between bound and unbound types. It is interesting to note that HasGenericArguments is deprecated and the compiler tells you that IsGenericType should be used instead:

warning CS0618: 'System.Type.HasGenericArguments' is obsolete: 
   'HasGenericArguments has been deprecated. Please use IsGenericType 
   instead - this will be removed before Whidbey  ships.'

The ContainsGenericParameters property name can be misleading, it returns true if a generic type is not bound but not when a generic type has generic parameter types. For example, Simple<Simple<string>> is parameterized by Simple<string> which is a generic And for this type, ContainsGenericParameters returns false .

Building the Right Name of a Generic Type

Once you hold a bound generic type, you get additional details on its type parameters when you call the GetGenericArguments() method. Since it's possible to define a type with multiple type parameters, this method returns an array of Type , each detailing the corresponding bound parameter. For an unbound generic type, GetGenericArguments() returns an array of Type , one for each unbound type parameter. You will see how to use them to determine constraints.

It is not too difficult to write a GetTypeName() method which computes the C#-like name of a type, either if it is a bound or an unbound generic type instead of the bracketed [] version returned by ToString() . Here's such a method.

string GetTypeName(Type type)
{
    // easy case first when not a generic type
    if (!type.IsGenericType)


    {
        return(type.Name);
    }
    // a little more complicated for 
    // unbound or bound generic types
    StringBuilder sbInfo = new StringBuilder();
    // get type name without the `n where n is 
    // the number of generic parameters
    sbInfo.Append(type.Name.Split('`')[0]);
    Type[] GenericParameters = type.GetGenericArguments();


    if (GenericParameters.Length > 0)
    {
        sbInfo.Append("<");
        for (
            int CurrentParameter = 0;
            CurrentParameter < GenericParameters.Length;
            CurrentParameter++
            )
        {
            sbInfo.Append(GetTypeName(GenericParameters[CurrentParameter]));


            if (CurrentParameter < GenericParameters.Length - 1)
                sbInfo.Append(", ");
        }
        sbInfo.Append(">");
    }
    return (sbInfo.ToString());
}

If the given type is not a generic, then obviously its Name property is returned. If it is a generic type, this code removes the parameters count before building the type of each generic parameter using Type.GetGenericArguments() . Since a generic type can be parameterized by another generic type, you must recursively call a helper method I've written, GetTypeName() on each parameter type. Bear in mind that it is perfectly possible to create a complicated object such as - there are no limits other than your ability to write sensible code!

Simple<Simple<int>> complicated = new Simple<Simple<int>>();

This example might look strange at first sight, but this kind of "over generic" types might well become common with the new generic collections provided by version 2.0 of the CLR in which you can store any type, be it generic itself.

You have two ways to get the definition of unbound generic types. First, the GetExportedTypes() and GetTypes() methods from Assembly naturally return unbound generic types defined in an assembly. Second, from a bound generic type, you can get its unbound definition via the GetGenericTypeDefinition() method. For example, typeof(Simple<Simple<string>>).GetGenericTypeDefinition() returns the Type describing Simple<T> . You should always take care of checking if the type is generic via IsGenericType before calling GetGenericTypeDefinition() . If you don't and the type is not a generic type, you get a System.InvalidOperationException with " Operation is not valid due to the current state of the object. " as the message.

Don't Restrict Generic Types!

Generic types in Microsoft .NET allow you to define restrictions on their unbound type parameters. Using the same example, for Simple<T> , you can require that its unbound type parameter T derives from a certain type or implements some interfaces using a particular syntax. Let's see a more complex example:

    public class Parent<T>
        where T: IConvertible, new()


    {
        // the constructor constraint allows you to instanciate an object T
        // such as T element = new T();
    }
    public class Child<K,T>: Parent<T>, IDisposable
        where K: IFormattable
        where T: IConvertible, new()


    {
        public void Dispose()
        {
        }
    }

To instantiate a Parent class object, an eligible type T must implement the interface IConvertible and also provide a default constructor using the " , new() " syntax. The latter constraint allows your generic type to create an instance of that type parameter as a class member, for example. The Child type derives from the generic type Parent , implements IDisposable and its type parameter K must implement IFormattable . Even though Child derives from Parent , it is required to explicitly repeat the constraints defined at the Parent level, here T must implement IConvertible and provide a default constructor.

All this mess looks impossible to decipher through reflection! Unlike what it seems, the inheritance is not changed from what has already been explained previously in this article. The constraints are simply defined at the level of each type parameter. What does that mean? When you call GetGenericArguments() to list each type parameter, the Type objects you get provide the same details as a normal type.

In ReflectionHelper.cs , you can find the source code of the method GetTypeConstraints() which returns each constraint as a string in a C# format:

static public string[] GetTypeConstraints(Type type)
{
    StringCollection constraints = new StringCollection();
    // the constraints are defined as inheritance for each type parameter
    //  1. derive from a type different from System.Object
    //  2. implement a bunch of interfaces
    //  3. get the NewConstraint attribute
    foreach (Type genericParameterType in type.GetGenericArguments())
    {
        Type parentType = genericParameterType.BaseType;
        Type[] interfaces = genericParameterType.GetInterfaces();
        bool bNewConstraint = 
            (
                genericParameterType.GenericParameterAttributes 
                == 
                GenericParameterAttributes.DefaultConstructorConstraint
            );


        // the three conditions to detect constraints
        if (
            (parentType == typeof(object)) &&  // parent to derive from
            (interfaces.Length == 0) &&      // interfaces to implement
            (bNewConstraint)          // default constructor to support
            ) 
        {
            continue;
        }
        // we are sure to have constraint on this parameter
        StringBuilder sbText = new StringBuilder();
        sbText.AppendFormat("where {0} : ", genericParameterType.Name);


        // start with the parent class
        if (parentType != typeof(object))
        {
            sbText.Append(GetTypeName(genericParameterType.BaseType));


            if (
                (interfaces.Length > 0) || 
                (bNewConstraint)
                )
            {
                sbText.Append(", ");
            }
        }
        // get interface-based constraints: nothing new here...
        for (
            int currentInterface = 0; 
            currentInterface < interfaces.Length; 
            currentInterface++
            )
        {
            sbText.Append(GetTypeName(interfaces[currentInterface]));


            if (
                (currentInterface < interfaces.Length - 1) || 
                (bNewConstraint)
                )
            {
                sbText.Append(", ");
            }
        }
        // check the new() constraint through attribute
        if (bNewConstraint)


        {
            sbText.Append("new()");
        }
        constraints.Add(sbText.ToString());
    }
    string[] constraintsArray = new string[constraints.Count];
    constraints.CopyTo(constraintsArray, 0);
    return(constraintsArray);
}

The type constraint on its parent is retrieved through the BaseType property, if it returns a type different from "object". Each interface that must be implemented is discovered through the same GetInterfaces() method explained previously. The only new mechanism to use is the one dealing with the default constructor constraint. In that particular case, you simply check that GenericParameterAttributes has GenericParameterAttributes.DefaultConstructorConstraint as a value.

Note, that in earlier versions of .NET 2005, a NewConstraintAttribute attribute was attached to the type parameter, and calling the GetCustomAttributes() method was the way to detect it through Reflection, this is no more the case under Beta2.

Conclusion

This second article of the series about using reflection to compare different versions of the CLR demonstrated how to dump the contents of a given assembly, from the attributes attached to it up to each kind of type such as an interface, delegate, class, struct and enumeration. I have shown the API to use in order to follow the hierarchies of types and interfaces as well.

I described how to use reflection to detect and decypher generics, one of the new features of new version of .NET, with a special focus on constraints. The third and final article of the series will enter into the details of the members of types without forgetting to use the dark side of reflection (aka unmanaged COM-based reflection) to decypher P/Invoke signatures using managed C++.

Founders at Work



Add your comments

Please keep your comments relevant to this blog entry: inappropriate or purely promotional comments may be removed. To add hyperlink, please follow this example: "your link text":http://your.link.url