Thursday, August 28, 2008

Covariance versus Contravariance

Ok so I stumbled on to this subject the other day and thought it was worth noting. Take these simple classes:
public class First
{
  public String FirstOutput { get; set; }
}

public class Second : First
{
  public String SecondOutput { get; set; }
}

public class Third : Second
{
  public String ThirdOutput { get; set; }
}
So from this you can see that Third inherits Second which in turns inherits First. By terminology this would mean that Third is "smaller" than Second and First is "larger" than both. Here's an example of Covariance:
public class Covariance
{
  public Covariance()
  {
    
      Func<First> returnFirstFunc = ReturnFirst;
      //This works since the Func has a return type of First
    

      Func<Second> returnSecondFunc = ReturnThird;
      Second secondTest = returnSecondFunc();
      secondTest.FirstOutput = "First";
      secondTest.SecondOutput = "First";
      //This works since the Func has a return type of Third which is smaller
       //that Second.  Therefore anyone using this Func will expect a Second to
       //be returned and will only use the methods/properties that a Second object
       //would have.  Methods/Properties that Third has by inheritance.


      Func<Third> returnThirdFunc = ReturnSecond;
      //THIS WILL NOT WORK
       //Due to Covariance, the return of the method must be equal or smaller
       //that the expected type.  returnThirdFunc expects a Third or smaller object
       //but the ReturnSecond method returns a Second which is not smaller than Third.
       //Afterall, Third : Second
       //
       //Third thirdTest = returnThirdFunc();
       //Is the same as:
       //Third thirdTest = new Second();
  }

  private First ReturnFirst()
  {
      return new First();
  }

  private Second ReturnSecond()
  {
      return new Second();
  }

  private Third ReturnThird()
  {
      return new Third();
  }
}
Basically what this all means is that with return types, the return type must be smaller or equal to the field it's being set to. When you are dealing with Funcs, the return type must be smaller or equal to the return type for the method it's being set it. Why is that? Well think of it like this: It's your first day on the job and some guy tells you to write something with whatever returnFirstFunc() returns. Now you have no way to look at the code, so you can only know that it returns First. For all you know, it could return First, Second, or Third. So you would do this:
First someFirst;

someFirst = returnFirstFunc();  //Could return anything smaller than First
someFirst.FirstOutput;  //Completely legal and safe
But would you do this?
someFirst.ThirdOutput;
Of course not since you only can assume it is a First. Now let's do this in reverse. Say from the above example you were allowed to do this:
Func<Third> returnThirdFunc = ReturnSecond;
Could you do this?
Third third;

third = returnThirdFunc();
third.ThirdOutput;
Yeah you can't since the Second type doesn't have the ThirdOutput property. In short Covariance is the allowance of Smaller types or equal. If a method returns back Third, then you can use that method for anything that is Third or Smaller (Second, First, Object) but not for something Larger (Fourth, Fifth, ect).

No comments: