For type safety, C# 4.0 supports covariance/contravariance ONLY for type parameters marked with in or out.
If this extended to classes, you'd also have to mark type parameters with in our out and this would end up being very restrictive. This is most likely why the designers of the CLR chose not to allow it. For instance, consider the following class:
public class Stack<T>
{
int position;
T[] data = new T[100];
public void Push (T obj) { data[position++] = obj; }
public T Pop() { return data[--position]; }
}
It would be impossible to annotate T as either in our out, because T is used in both input and output positions. Hence this class could never covariant or contravariant - even in C# supported covariance/contravariant type parameters for classes.
Interfaces solve the problem nicely. We can have define two interfaces as follows, and have Stack implement both:
public interface IPoppable<out T> { T Pop(); }
public interface IPushable<in T> { void Push (T obj); }
Note that T is covariant for IPoppable and contravariant for IPushable. This means T can be either covariant or contravariant - depending on whether you cast to IPoppable or IPushable.
Another reason that covariance/contravariance would be of limited use with classes is it would rule out using type parameters as fields - because fields effectively allow both input and output operations. In fact, it would be hard to write a class that does anything useful at all with a type parameter marked as in or out. Even the simplest case of writing a covariant Enumerable implementation would present a challenge - how would you get source data into the instance to begin with?