O'Reilly logo

Visual C# 2005: A Developer's Notebook by Jesse Liberty

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Gain Flexibility with Delegate Covariance and Contravariance

Now it is legal to provide a delegate method with a return type that is derived (directly or indirectly) from the delegate's defined return type; this is called covariance . That is, if a delegate is defined to return a Mammal, it is legal to use that delegate to encapsulate a method that returns a Dog if Dog derives from Mammal and a Retriever if Retriever derives from Dog which derives from Mammal.

Note

Covariance allows you to encapsulate a method with a return type that is directly or indirectly derived from the delegate's return type.

Similarly, now it is legal to provide a delegate method signature in which one or more of the parameters is derived from the type defined by the delegate. This is called contravariance. That is, if the delegate is defined to take a method whose parameter is a Dog you can use it to encapsulate a method that passes in a Mammal as a parameter, if Dog derives from Mammal.

How do I do that?

Covariance and contravariance give you more flexibility in the methods you encapsulate in delegates. The use of both covariance and contravariance is illustrated in Example 1-10.

Note

Contravariance allows you to encapsulate a method with a parameter that is of a type from which the declared parameter is directly or indirectly derived.

Example 1-10. Using covariance and contravariance

#region Using directives
   
using System;
using System.Collections.Generic;
using System.Text;
   
#endregion
   
namespace CoAndContravariance
{
   
    class Mammal
    {
        public virtual Mammal ReturnsMammal( )
        {
            Console.WriteLine("Returning a mammal");
            return this;
        }
    }
   
    class Dog : Mammal
    {
   
        public Dog ReturnsDog( )
        {
            Console.WriteLine("Returning a dog");
            return this;
        }
   
    }
   
    class Program
    {
   
        public delegate Mammal theCovariantDelegate( );
        public delegate void theContravariantDelegate(Dog theDog);
   
   
        private static void MyMethodThatTakesAMammal(Mammal theMammal)
        {
            Console.WriteLine("in My Method That Takes A Mammal");
        }
   
        private static void MyMethodThatTakesADog(Dog theDog)
        {
            Console.WriteLine("in My Method That Takes A Dog");
        }
   
   
        static void Main(string[  ] args)
        {
            Mammal m = new Mammal( );
            Dog d = new Dog( );
   
            theCovariantDelegate myCovariantDelegate = 
                new theCovariantDelegate(m.ReturnsMammal);
            myCovariantDelegate( );
   
            myCovariantDelegate = 
                new theCovariantDelegate(d.ReturnsDog);
            myCovariantDelegate( );
   
            theContravariantDelegate myContravariantDelegate =
                new theContravariantDelegate(MyMethodThatTakesADog);
            myContravariantDelegate(d);
   
             myContravariantDelegate =
                new theContravariantDelegate(MyMethodThatTakesAMammal);
            myContravariantDelegate(d);
        }
    }
}

Output:

Returning a mammal
Returning a dog
in My Method That Takes A Dog
in My Method That Takes A Mammal

What just happened?

The Program class in Example 1-10 declares two delegates. The first is for a method that takes no parameters and returns a Mammal:

public delegate Mammal theCovariantMethod( );

In the run method, you declare an instance of Mammal and an instance of Dog:

               Mammal m = new Mammal( );
Dog d = new Dog( );

You are ready to create your first instance of theCovariantDelegate:

Note

Earlier in the file you declared Dog to derive from Mammal.

theCovariantDelegate myCovariantDelegate = 
   new theCovariantDelegate(m.ReturnsMammal);

This matches the delegate signature (m.ReturnsMammal() is a method that takes no parameters and returns a Mammal), so you can invoke the method through the delegate:

myCovariantDelegate( );

Now you use covariance to encapsulate a second method within the same delegate:

myCovariantDelegate = 
   new theCovariantDelegate(d.ReturnsDog);

This time, however, you are passing in a method (d.ReturnsDog( )) that does not return a Mammal; it returns a Dog that derives from Mammal:

public Dog ReturnsDog( )
{
   Console.WriteLine("Returning a dog");
   return this;
}

That is covariance at work. To see contravariance, you declare a second delegate to encapsulate a method that returns null and takes a Dog as a parameter:

public delegate void theContravariantDelegate(Dog theDog);

Your first instantiation of this delegate encapsulates a method with the appropriate return value (void) and parameter (Dog):

myContravariantDelegate =
   new theContravariantDelegate(MyMethodThatTakesADog);

You can invoke the method through the delegate. Your second use of this delegate, however, encapsulates a method that does not take a Dog as a parameter, but rather, takes a Mammal as a parameter:

Note

Notice that with contravariance, you can pass in an object of the base type as a parameter where an object of the derived type is expected.

               theContravariantDelegate myContravariantDelegate =
   new theContravariantDelegate(MyMethodThatTakesAMammal);

MyMethodThatTakesAMammal is defined to take a Mammal, not a Dog, as a parameter:

private void MyMethodThatTakesAMammal(Mammal theMammal)
{
   Console.WriteLine("in My Method That Takes A Mammal");
}

Again, this works because a Dog is a Mammal and contravariance allows you to make this substitution.

What about . . .

...contravariance? I get why with covariance I can return a Dog (a Dog is a Mammal), but why does contravariance work the other way? Shouldn't it accept a derived type when it expects a base type?

Contravariance is consistent with Postel's Law: "Be liberal in what you accept, and conservative in what you send." As the client, you must make sure that what you send to the method will work with that method, but as the implementer of the method you must be liberal and accept the Dog in any form, even as a reference to its base class.

...what about reversing the usage of covariance and returning a base type where a derived type is expected? Can I do that?

Note

Dr. Jonathan Bruce Postel (1943-1998), contributor to Internet Standards.

No, it works in only one direction. You can, however, return a derived type where a base type is expected.

...what about reversing the usage of contravariance and passing in a derived type where a base type is expected?

You can't do that, either. Again, it works in only one direction. You can, however, pass in a base type where a derived type is expected.

Where can I learn more?

Internet newsgroups contain numerous discussions about the advantages and disadvantages of object-oriented languages that support covariance and contravariance. For details about using covariance and contravariance in C# programs, see the MSDN Help file pages for the two topics.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required