Saturday, August 18, 2012

Contravariance in C# allows for implicitly casting a parent to a child.

int foo = 3;
double bar = foo;
decimal baz = (decimal)bar;

 
 

Notice that above we may implicitly cast foo to bar but that we must explicitly cast bar to baz. The boundaries and differentiation in this case are defined by type, but implicit casts may also be unobtainable given the nature of how the cast works. For example, the following won't fly even though we just saw an example of explicitly casting an int to a double.

List<int> qux = new List<int>() {1, 2, 3};
List<double> quux = qux;

 
 

Whenever we can implicitly cast a more-specific object to a less-specific object, and there are myriads of C# situations in which as much will and will not work, we have an example of covariance. Anytime you may upcast from a child to a parent implicitly you have an example of covariance. Here is an example:

Calico calico = new Calico();
calico.Name = "Patches";
Cat cat = calico;

 
 

If Calico inherits from Cat, you may get away with the casting above, however, as you might imagine, the following code cannot compile:

Cat cat = new Cat();
cat.Name = "Stripes";
Calico calico = cat;

 
 

What we attempt, in vain, immediately above is called contravariance. Any time you implicitly cast from parent to child you have contravariance. In C#, there are many ways in which covariance may be without issue, but when it comes to contravariance, C# 4.0 in a Nutshell by Joseph and Ben Albahari illuminates that there are only two possibilities:

  1. The first is of delegates like so. In this example DoSomethingErOther is a method that takes a Cat and returns a string. We may implicitly cast a Func<Cat, string> to a Func<Calico, string>. If this seems like covariance in disguise in that we are going to ultimately hand a Calico into a method that has Cat in its signature, well, I'm right there with you. I agree.
    Func<Cat, string> corge;
    corge = DoSomethingErOther;
    Func<Calico, string> grault;
    grault = corge;
    Calico calico = new Calico();
    calico.Name = "Patches";
    string garply = grault(calico);

     
  2. For my second trick, I'll need a class and an interface:
    • namespace Whatever.Models
      {
         public class CatMagicMaker : IMagicMaker<Cat>
         {
            public string CastSpell(Cat cat)
            {
               return cat.Name;
            }
         }
      }

       
    • namespace Whatever.Models
      {
         public interface IMagicMaker<in T>
         {
            string CastSpell(T obj);
         }
      }

       
    The other way to do contravariance (it hinges on the in keyword):
    IMagicMaker<Cat> waldo = new CatMagicMaker();
    IMagicMaker<Calico> fred = waldo;
    string plugh = fred.CastSpell(calico);

No comments:

Post a Comment