Safe Covariance and Contravariance in .NET 4.0

Introduction

Welcome to this installment of the .NET Nuts & Bolts column! The focus of this article will be on safe covariance and contravariance changes that will be released as a part of the upcoming 4.0 version of the .NET Framework and how this affects the .NET Developer. In order to run the examples contained within this article you'll need to use an early preview such as the .NET Framework SDK 4.0 beta or CTP release.

Invariance, covariance, and contravariance are all topics you have likely been exposed to, but likely do not recognize by name. Invariance means there must be an exact match for a formal type and cannot be assigned a more or less derived type. Covariant involves being able to use a more derived type as a substitute, e.g. a string in place of an object. Contravariance involves the ability to use a less derived type as a substitute, e.g. an object in place of a string. In many cases, the .NET framework supports covariant parameters and contravarient return types. However, the game changes with arrays and collections as they behave differently than other types when it comes to covariance and contravariance. It is very possible you have run in to this difference in behavior. You likely worked around it, but were left scratching your head at the difference in behaviors.

Covariance in Arrays and Collections

.NET arrays are covariant, which means a string array can be passed as a less derived object array. However, .NET string arrays are not safely covariant. Code involving string arrays being used as a less derived type will compile, but will fail at runtime. For arrays involving reference types, every assignment has a dynamic type check that checks what you are assigning is the same type as the actual array. If the assignment involving a reference type does not match it will generate an exception. However, the compiler does nothing to keep you from making mistakes in how you treat the objects.

  string[] testStrings = {"Item1", "Item2", "Item3", "Item4", "Item5"};
  Process (testStrings);
  
  void Process(object[] objects)
  {
  	objects[0] = "Hello";		// Ok
  	objects[1] = new Button();	// Generates an exception
  }

Generics

C# 2.0 introduced generics. When the example above is written using generics it would fail at compile time because generics are invariant. Invariant means the type only matches itself, so different types are not a substitute for the formal type. You cannot substitute either a more or less derived type. For example, you would think a variable, method declaration, and method call like the example below would work because any collection that implements IEnumerable<T> because any T would derive from object. However, since Generics are invariant it will not work.

  List<STRING> strings = GetStringList();
  Process(strings);
  
  void Process(IEnumerable<OBJECT> objects)
  {
  	// …
  }

Safe Covariance and Contravariance

The .NET Framework 4.0 introduces some behavior changes and reuses some language keywords (out and in) to put you in control of covariance and contravariance. Out is used for covariance, which means the types can be treated as less derived. It is applied to output positions only. The example below demonstrates how covariance is used to allow an IEnumerable of strings to be treated as a lesser derived IEnumerable of objects.

  public interface IEnumerable<out T>
  {
  	IEnumerator<T> GetEnumerator();
  }
  
  public interface IEnumerator<out T>
  {
  	T Current { get; }
  	bool MoveNext();
  }
  
  // Types can be treated as less derived
  IEnumerable<string> strings = GetStrings();
  IEnumerable<object> objects = strings;

The in keyword is used for contravariance. The example below demonstrates how the input position is used to treat an array as a more derived type.

  public interface IComparer<in T>
  {
  	int Compare(T x, T y);
  }
  
  // Types can be treated as more derived
  IComparer<object> objComp = GetComparer();
  IComparer<string> strComp = objComp;

Additionally, generics are no longer just invariant and examples like the one above on generics will now behave as you would originally expect in certain cases of covariance.

  List<string> strings = GetStringList();
  Process(strings);
  
  void Process(IEnumerable<object> objects)
  {
  	// IEnumerable<T> is read-only and therefore safely covariant
  }

Summary

You have learned about safe covariance and contravariance being introduced as a part of the upcoming .NET Framework 4.0 release as well as changes in behavior with Generics. For those that have run in to the need for it the changes will be a welcome addition.

Future Columns

The topic of the next column is yet to be determined. It will most certainly be something focused towards the next release of the .NET Framework. If you have something in particular that you would like to see explained here you could reach me at mark .strawmyer@crowehorwath.com.



About the Author

Mark Strawmyer

Mark Strawmyer is a Senior Architect of .NET applications for large and mid-size organizations. He specializes in architecture, design and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C# for the fifth year in a row. You can reach Mark at mark.strawmyer@crowehorwath.com.

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: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds