Archive

Archive for March, 2011

Covariance and Contravariance in .NET and Java – Part 2 – Constraints and Wildcards

March 22, 2011 2 comments

Sometimes we want to put constraints on the types that are valid substitutes for our formal type parameters.
Java implements constraints using wildcards. These are subdivided into unbounded wildcards and bounded wildcards. In the following, we will take a look at the wilcards and their C# equivalents.

1. Unbounded Constraints

1.1 Java

The folllowing code uses an unbounded wildcard to print a collection of any type:

	static void PrintAll(ICollection<?> list) {
		for (Object t : list) {
			System.out.println(t);
		}
	}

The ? wildcard was primarily added to allow working with legacy code that used non-generic collections. You might think that instead of an undbounded wilcard you could just use a generic method instead with an unconstrained type parameter, however there is an important semantic difference. Any ? can be assigned to Object, since ? <: Object is true for all ?. The converse however, Object <: ? is clearly not true, which is why using ? prohibits the following:

	static void  PrintAndAdd(Collection&lt;?&gt; list, Object item) {
	    for (Object t : list) {
	        System.out.println(t);
	    }
	    list.add(item);
	}

This is different from an implementation using a generic method, which will allow this just fine.

	static  void PrintAndAddGeneric(Collection list, T item) {
	    for (T t : list) {
	        System.out.println(t);
	    }
	    list.add(item);
	}

1.2 C#

In C#, there’s no direct equivalent to the unbounded wildcard, so the closest we can get is using a plain generic method:

static void PrintAll (IEnumerable list) {
    foreach (T t in list) {
        Console.WriteLine (t);
    }
}

2. Upper Bound Constraints

2. 1Java

The next Java type of wildcards java offers are so-called bounded wildcards. Bounded wildcards let you specify an upper or lower bound for your formal type parameter. Let’s first see them in action:
Suppose we haved the following classes

	class Vehicle {}
	class Car extends Vehicle {}
	class Porsche extends Car {}

and have a special print funtion that can only print cars. We can use a bounded wildcard here:

	static void PrintCars(Collection&lt;? extends Car&gt; list) {
	    for (Car c : list) {
	        System.out.println(c);
	    }
	}
	

This time, we could also use a generic method with a concrete formal type parameter and there’s no difference in semantics.

	static &lt;T extends Car&gt; void PrintCars(Collection list) {
	    for (Car c : list) {
	        System.out.println(c);
	    }
	}

Remember that we said Object is the maximum in our set of types? Now it is easy to see what concepts extends actually maps to.

“? extends T” means T is an upper bound for ?, so ? only matches a type U if U <: T.

Let’s assume we have a List, then Porsche <: Car, so we can call PrintCars(List) just fine.

2.2 C#

What is an equivalent C# construct? We can use a formal type parameter in conjunction with a where constraint to get the desired effect:

	static void PrintCars&lt;T&gt;( ICollection&lt;T&gt; list )
		where T : Car
	{
		foreach (Car c in list)
		{
			Console.WriteLine( c );
		}
	}

3. Lower Bound Constraints

3.1 Java

So we can specify an upper bound constraint. As always when there’s an upper bound, we also want to see if there’s a lower bound. Let’s establish a new sample. Suppose we have a collection and we want to flush all but the last element into a sink. Here’s the code:

	interface Sink {
		void flush(T t);
	}

	class ConcreteSink implements Sink {
		public void flush(T t) {}
	}

	static  T flushAll(Collection c, Sink&lt;super T&gt; sink) {
		T last = null;
		for (T t : c) {
			last = t;
			sink.flush(last);
		}
		return last;
	}

	public void foo(){
		Sink&lt;Vehicle&gt; sink = new ConcreteSink&lt;Vehicle&gt;();
		List&lt;Car&gt; list = Arrays.asList(new Car());

		flushAll(list, sink);
	}

The Sink<> we might have can be specialized for all sorts of vehicles, but when we have a collection of Porsches and a Sink, we’d expect we can flush the Porsche. In fact this code is perfectly fine, so:

“? super T” means T is a lower bound for ?, so ? only matches a type U if T <: U.

3.2 C#

In C# we can create an upper bound constraint by using two formal type parameters and then restricting one against the other. Here’s the sink example in equivalent C#:

	static T flushAll&lt;T, U&gt;(ICollection&lt;T&gt; c, ISink&lt;U&gt; sink) 
        where T : U
    {
		T last = default(T);
		foreach (T t in  c) {
			last = t;
			sink.flush(last);
		}
		return last;
	}

	public void foo(){
		ISink&lt;Vehicle&gt; sink = new ConcreteSink&lt;Vehicle&gt;();
        List&lt;Car&gt; list = new List&lt;Car&gt;() { new Car() };

		flushAll(list, sink);
	}

By specifying T <: U, we do effetively say that U is a superclass of T, which is just the same as “? super T”.

Final Words

In this blog post we have seen the varios constraints we can put on generic types and what constructs are equivalent. The list of constraints presented here is not exhaustive. There are additional constraints that .NET and Java offer respectively and they are tightly bound to the way generics are implemented on each platform. We have focused on the constraints that put constraints on the type relationships between formal type paramters. As we will see in a future post, this is important ground work to see where covariance and contravariance come in to play, and to what extend we have already seen it implemented by using constraints.

Categories: .NET vs. Java
%d bloggers like this: