C# Generics Part 4/4: Delegates, Events, Reflection, Attributes, IL, Generics in the BCL

The following article is excerpted from the book Practical .NET2 and C#2. The first part of this article is located here.

Delegates, events and generics

Introduction

As with all nested types, a delegate class can use the parameter types of the nesting class:

Example 33

class C<T> {
    public delegate T GenericDelegate( T t );
    public static T Fct( T t ) { return t; }
}
class Program {
   static void Main() {
       C<string>.GenericDelegate genericDelegate = C<string>.Fct;
       string s = genericDelegate( "hello" );
   }
}

A delegate class can also define its own parameter types as well as their constraints:

Example 34

public delegate U GenericDelegate<U>( U u ) where U : class;
class C<T> {
   public static T Fct( T t ) { return t; }
}
class Program {
   static void Main() {
      GenericDelegate<string> genericDelegate = C<string>.Fct;
      string s = genericDelegate( "hello" );
   }
}

Generic delegates and generic methods

When assigning a generic method to a generic delegate object, the C#2 compiler is capable of inferring the parameter types of the generic method from the parameter types of the generic delegate object. This is illustrated by the following example:

Example 35

delegate void GenericDelegateA<U>(U u);
delegate void GenericDelegateB(int i);
delegate U    GenericDelegateC<U>();
class Program {
   static void Fct1<T>(T t) { return; }
   static T    Fct2<T>() { return default(T); }
   static void Main() {
      GenericDelegateA<string> d1 = Fct1; // The compiler infers 
                                          // 'Fct1<string>'.
      GenericDelegateB d2 = Fct1; // The compiler infers 'Fct1<int>'.
      GenericDelegateC<string> d3 = Fct2<string>; // OK but no
                                                  // inference.

      // Compilation error: The type arguments for method
      // 'Program.Fct2<T>()' cannot be inferred from the usage.
      GenericDelegateC<string> d4 = Fct2;
   }
}

As illustrated in this example, there is never any inference on the parameter types of a generic delegate object.

Contravariance, covariance, delegates and generics

Here, we will expose a new functionality of delegate objects which will come in handy later. In C#2, delegate objects support the notion of contravariance on their arguments and the notion of covariance on their return type. This is illustrated below:

Example 36

class Base { }
class Derived : Base { }
delegate Base DelegateType ( Derived d );
class Program{
   static Derived Handler ( Base b ) { return b as Derived; }
   static void Main() {
      // Notice that the 'Handler()' method signature is not ...
      // ... the same as the 'DelegateType' signature.
      DelegateType delegateInstance = Handler;
      Base b = delegateInstance( new Derived() );
   }
}

It is legitimate for this program to compile properly. Think about it in terms of contract:

  • The Handler(Base) method has a contract less strict on its input than the one proposed by the DelegateType(Derived) delegate class. An instance of Handler() method without risking an invalid downcast. This is contravariance.
  • The Derived Handler() method has a more strict contract on its output than the one proposed by the Base DelegateType() delegate class. There also, an instance of DelegateType can reference the Handler() method without risking an invalid downcast. This is covariance.

Now, lets assume that the notions of covariance and of contravariance did not exist and that we wished to called the Derived Handler(Base) method through a delegate object of type Base delegate(Derived). We could in fact use a generic delegate object like this:

Example 37

class Base { }
class Derived : Base { }
delegate B DelegateType<B,D>(D d);
class Program {
   static Derived Handler(Base b){return b as Derived;}
   static void Main() {
      DelegateType<Base, Derived> delegateInstance = Handler;
      // The in reference is implitly casted from 'Derived' to 'Base'.
      // The out reference is implitly casted from 'Derived' to 'Base'.
      Base b = delegateInstance( new Derived() );
   }
}

Events and generic delegates

Generic delegates can be used to avoid the definition of multiple delegates in order to type events. The following example shows that with a single generic delegate class, you can type all events which take in a single 'sender' parameter and one argument:

Example 38

delegate void GenericEventHandler<U,V>( U sender, V arg );
class Publisher {
   public event GenericEventHandler<Publisher,System.EventArgs> Event;
   public void TriggerEvent() { Event(this, System.EventArgs.Empty); }
}
class Subscriber {
   public void EventHandler( Publisher sender, System.EventArgs arg ){}
}
class Program {
   static void Main() {
      Publisher publisher = new Publisher();
      Subscriber subscriber = new Subscriber();
      publisher.Event += subscriber.EventHandler;
   }
}

Reflection, attributes, IL and generics

Generics and the System.Type class

Remember that an instance of System.Type can be obtained either using the typeof operator or by calling the object.GetType() method. With .NET 2, an instance of System.Type can reference an opened or closed generic type.

Example 39

using System;
using System.Collections.Generic;
class Program {
    static void Main() {
        List<int> list = new List<int>();
        Type type1 = list.GetType();
        Type type2 = typeof( List<int> );
        Type type3 = typeof( List<double> );
        // type4 is an open generic type.
        Type type4 = type3.GetGenericTypeDefinition();
        System.Diagnostics.Debug.Assert( type1 == type2 );
        System.Diagnostics.Debug.Assert( type1 != type3 );
        System.Diagnostics.Debug.Assert( type3 != type4 );
    }
}

The System.Type class supports new methods and properties dedicated to generics:

public abstract class Type : System.Reflection.MemberInfo,
                             System.Runtime.InteropServices._Type,
                             System.Reflection.IReflect
{
   // Returns from a generic type a new generic Type instance based
   // on the provided parameter types.
   public virtual System.Type MakeGenericType(
                                params System.Type[] typeArgs);

   // Returns an array of Type objects which represent the parameter 
   // types of a generic type.
   public virtual System.Type[] GetGenericArguments();

   // Returns an open generic type from a generic type.
   public virtual System.Type GetGenericTypeDefinition();

   // Returns true if it's an open generic type.
   public virtual bool IsGenericTypeDefinition { get; }

   // Returns true if some parameter types are not specified.
   public virtual bool ContainsGenericParameters { get; }

   // Returns true if the current type is a parameter type not 
   // specified for a generic type or method.
   public virtual bool IsGenericParameter { get; }

   //---------------------------------------------------------------
   // Following members can only be called on Type instances for
   // which the 'IsGenericParameter' property returns true.

   // Returns the 0-based position in the list of parameter types.
   public virtual int GenericParameterPosition { get; }

   // Returns the generic method which declares the parameter type.
   // Returns null if not declared in a generic method.
   public virtual System.Reflection.MethodBase DeclaringMethod { get; }

   // Returns derivation constraints on the current parameter type.
   public virtual System.Type[] GetGenericParameterConstraints();

   // Returns constraints other than the derivation ones.
   public virtual System.GenericParameterAttributes
              GenericParameterAttributes { get; }
   ...
}

Here is an example of the use of this class used to locate the definition of a generic type:

Example 40

using System;
using System.Reflection;
class Program {
   static void WriteTypeConstraints(Type type ){
      string[] results = new string[type.GetGenericArguments().Length];
      // For each parameter types.
      foreach (Type t in type.GetGenericArguments()) {
         // If 't' is a parameter type not specified?
         if ( t.IsGenericParameter ) {
            int pos = t.GenericParameterPosition;
            Type[] derivConstraints = t.GetGenericParameterConstraints();
            MethodBase methodBase = t.DeclaringMethod;
            GenericParameterAttributes attributes = 
                        t.GenericParameterAttributes;
            results[pos] = "   where " + t.Name + ":";
            if ((GenericParameterAttributes.ReferenceTypeConstraint &
                 attributes) != 0 ) {
               results[pos] += "class,";
            }
            if((GenericParameterAttributes.
                NotNullableValueTypeConstraint & attributes) != 0 ) {
               results[pos] += "struct,";
            }
            foreach (Type derivConstraint in derivConstraints) {
               results[pos] += derivConstraint.Name + ",";
            }
            if ((GenericParameterAttributes.
                 DefaultConstructorConstraint & attributes) != 0 ) {
               results[pos] += "new()";
            }
         }    // end -- If 't' is a parameter type not specified?
      }       // end -- For each parameter types.
      Console.WriteLine(type.Name);
      foreach (string result in results)
         if (result != null)
            Console.WriteLine(result);
   }

   class Bar{}
   class Foo : Bar, IDisposable{ public void Dispose() {} }
   class C&lt;U, V&gt;
       where U : Bar, IDisposable, new()
       where V : struct {}

   static void Main() {
      WriteTypeConstraints( typeof(C&lt;Foo,int&gt;) );
      WriteTypeConstraints( typeof(C&lt;Foo,int&gt;).
         GetGenericTypeDefinition());
   }
}

This program displays:

C`2
C`2
where U:Bar,IDisposable,new()
where V:struct,ValueType,new()

We can see that the constraint forcing the type to be a value type actually adds the constraints of deriving from ValueType and of implementing a default constructor.

C# Generics Part 4/4: Delegates, Events, Reflection, Attributes, IL, Generics in the BCL

Generics and the System.Reflection.MethodBase and System.Reflection.MethodInfo classes

The System.Reflection.MethodBase and System.Reflection.MethodInfo (which derives from MethodBase) classes have evolved in order to support the concept of generic methods. Here are the new members:

public abstract class MethodBase :
       System.Reflection.MemberInfo,
       System.Runtime.InteropServices._MethodBase {

   // Returns an array containing type parameters of a generic method.
   public virtual System.Type[] GetGenericArguments();

   // Returns true if it's an open generic method.
   public virtual bool IsGenericMethodDefinition { get; }

   // Returns true if some parameter types are not specified.
   public virtual bool ContainsGenericParameters { get; }
   ...
}

public abstract class MethodInfo :
       System.Reflection.MemberBase,
       System.Runtime.InteropServices._MethodInfo {

   // Returns a new generic MethodInfo from a generic method
   // with the provided parameter types.
   public virtual System.Reflection.MethodInfo MakeGenericMethod(
                          params System.Type[] typeArgs);

   // Returns an open generic type from a generic type.
   public virtual System.Reflection.MethodInfo GetGenericMethodDefinition();
   ...
}

The following program shows how to create a late bind on a generic method and how to invoke it after having resolved the parameter types:

Example 41

using System;
using System.Reflection;
class Program{
   public class Bar{}
   public class Foo : Bar, IDisposable{ public void Dispose() { } }
   public static void Fct<U, V>()
       where U : Bar, IDisposable, new()
       where V : struct {
      Console.WriteLine( typeof(U).Name );
      Console.WriteLine( typeof(V).Name );
   }
   static void Main() {
      Type typeProgram = typeof(Program);
      MethodInfo methodGenericOpen = typeProgram.GetMethod("Fct", 
                      BindingFlags.Static | BindingFlags.Public);
      //  Specify parameter types.
      MethodInfo methodGenericClosed = methodGenericOpen.MakeGenericMethod(
             new Type[] { typeof(Foo), typeof(int) } );
      System.Diagnostics.Debug.Assert (
             methodGenericClosed.GetGenericMethodDefinition() == 
             methodGenericOpen );
      methodGenericClosed.Invoke (
             null,  //  It is null because we call a static method.
             new object[0]); // No parameters in.
   }
}

This program displays:

Foo
Int32

Attributes and generics

An attribute which can be used to mark a general method can also be used on a generic method.

An attribute which can mark a general type can also be used on a generic type.

The System.AttributeTargets enumeration now has the new value GenericParameter which allows specifying that an attribute can be used to mark a parameter type. For example:

Example 42

[System.AttributeUsage(System.AttributeTargets.GenericParameter)]
public class A : System.Attribute{}
class C<[A]U, V> { }

An attribute class cannot be a generic class. Thus, a generic class cannot directly (or indirectly) derive from System.Attribute.

An attribute class can use generic types and also define generic methods:

Example 43

class C<U, V> { }
public class A : System.Attribute{
   void Fct1(C<int, string> c) { }
   void Fct2<X>() { }
}

The IL Language and generics

Support for generics implies changes at the CLR level but also changes within the IL language, the CTS and also in the metadata contained in assemblies.

In the body of methods of a generic type or within the scope of a generic method, the IL languages uses the !x notation to name a type parameter located at position x (0 based index) in the list of the parameter types.

New IL instructions have been added such stelem.any and ldelem.any for the access of the elements of a type parameter array (they complete the stelem.i2, ldelem.i2, stelem.i4, ldelem.i4, stelem.ref, ldelem.ref... instruction family). Certain IL instructions such as stloc.x or ldloc.x (which allows the manipulation of local variables) were already not taking into account the type of the manipulated values. They were then already ready for generics and only their interpretation by the JIT compiler has evolved.

Only two metadata tables have been added to the list of the metadata tables of an assembly containing generic types or methods:

  • The GenericParam table allows the description of the parameter types.
  • The GenericParamConstraint table allows the description of derivation constraints.

The value/reference and default constructor constraints are not stored in an attribute or a metadata table. They are simply contained in the name of the generic type or method. Hence, the following classes...

class C1<T> where T : new() {...}
class C2<T> where T : class {...}
class C3<T> where T : struct {...}

...as named as follows in IL code:

... C1<(.ctor) T> {...}
... C2<(class) T> {...}
... C3<(value type, .ctor, [mscorlib]System.ValueType) T> {...}

Generics in the.NET 2 framework

Object serialization and generics

It is possible to serialize and deserialize an instance of a generic type. In this case, it is required that the list of parameter types for the serialized object be identical to the parameter type list for the deserialized object. For example:

Example 44

using System;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
[Serializable]
public class C&lt;T&gt;{
   private T m_t;
   public T t { get { return m_t; } set { m_t = value; } }
}
class Program{
   static void Main() { 
      C&lt;int&gt; objIn = new C&lt;int&gt;();
      objIn.t = 691;
      IFormatter formatter = new BinaryFormatter();
      Stream stream = new FileStream( "obj.bin", FileMode.Create, 
                                      FileAccess.ReadWrite);
      formatter.Serialize(stream, objIn);
      stream.Seek(0, SeekOrigin.Begin);
      C&lt;int&gt; objOut = (C&lt;int&gt;)formatter.
         Deserialize( stream );
      // Here, objOut.t is equal to 691.

      // This line raises a SerializationException.
      C&lt;long&gt; objOut2 = (C&lt;long&gt;) formatter.
         Deserialize( stream );
      stream.Close();
   }
}

.NET Remoting and generics

It is possible to consume an instance of a closed generic type using the .NET Remoting technology whether you are in CAO or WKO modes:

// Define a generic class that can be used with .NET remoting.
public class Server<T> : MarshalByRefObject{}
...
   // Server side code
   RemotingConfiguration.RegisterActivatedServiceType(
      typeof( Serveur<int>) );
   RemotingConfiguration.RegisterWellKnownServiceType(
      typeof( Serveur<string>), "MyService",
      WellKnownObjectMode.SingleCall );
...
   // Client side code
   RemotingConfiguration.RegisterActivatedClientType(
      typeof( Serveur<int>), url );
   RemotingConfiguration.RegisterWellKnownClientType(
      typeof( Serveur<string>), url );

If you wish to use the client-side or server-side configuration files, you must specify the parameter types used:

// Server side
   <service>
      <activated
       type="ServerAssembly.Server[[System.Int32]],ServerAssembly"/>
   </service>
// Client side
   <client url="...">
      <activated
       type="ServerAssembly.Server[[System.Int32]],ServerAssembly"/>
   </client>

The double square brackets syntax allows you to specify a parameter type list:

type="ServeurAssembly.Serveur[[System.Int32],[System.String]],
      ServeurAssembly"

The System.Activator class also supports generics. Know that when you use this class with a generic type, you cannot use the CreateInstance() and CreateInstanceFrom() overloads in which you specify the name of the types using strings.

Collections and generics

The collection classes part of the .NET framework is the topic of chapter 15. They have been revised to take advantage of generics. The System.Collections namespace is supported for compatibility issues. However, there are no reasons to prefer a System.Collections type to a System.Collections.Generic type. Here is a correspondence table between the System.Collections and System.Collections.Generic namespaces.

System.Collections.Generics System.Collections
Comparer<T> Comparer
Dictionary<K,T> HashTable
List<T>
LinkedList<T>
ArrayList
Queue<T> Queue
SortedDictionary<K,T>
SortedList<K,T>
SortedList
Stack<T> Stack
ICollection<T> ICollection
IComparable<T> System.IComparable
IComparer<T> IComparer
IDictionary<K,T> IDictionary
IEnumerable<T> IEnumerable
IEnumerator<T> IEnumerator
IList<T> IList

Domains which don't support generics

The notions of web service and serviced component (i.e. COM+, Enterprise Services) do not support the concept of generics as neither standard currently supports generics.

[cover.jpg] The preceding article is excerpted from the book Practical .NET2 and C#2 by Patrick Smacchia. Publisher: Paradoxal Press. ISBN: 0-9766132-2-0. (Browse and download the 647 listings and sample chapters.)


About the Author

Patrick Smacchia

Patrick Smacchia is a .NET MVP involved in software development for over 15 years. He is the author of Practical .NET2 and C#2 (http://www.PracticalDOT.NET), a .NET book conceived from real world experience with 647 compilable code listings. After graduating in mathematics and computer science, he has worked on software in a variety of fields including stock exchange at Societe Generale, airline ticket reservation system at Amadeus as well as a satellite base station at Alcatel. He's currently a software consultant and trainer on .NET technologies as well as the author of the freeware NDepend which provides numerous metrics and caveats on any compiled .NET (http://www.NDepend.com) application.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: September 16, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you starting an on-premise-to-cloud data migration project? Have you thought about how much space you might need for your online platform or how to handle data that might be related to users who no longer exist? If these questions or any other concerns have been plaguing you about your migration project, check out this eSeminar. Join our speakers Betsy Bilhorn, VP, Product Management at Scribe, Mike Virnig, PowerSucess Manager and Michele …

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds