Before introducing the generic covariance and contravariance of. NET 4, we will introduce the variance concept of the programming language type system. To put it simply, covariance allows you to replace a class of the parent layer with a more specific class. In C #, arrays of reference types are covariant, which was learned from Java at that time. For example:
Namespace variance2
{
Public class Animal {}
Public class Dog: Animal {}
Class Program
{
Static void Main (string [] args)
{
Animal [] animals = new Dog [10];
Animals [0] = new Animal ();
}
}
} When initializing the animals array, you can use its subclass Dog, which is covariance. This code can be compiled, but the following error is reported during running:
Attempted to access an element as a type incompatible with the array.
Once the array is assigned a Dog value, it is actually a Dog array, So Animal cannot be assigned to it. A type error occurs when a code segment can be compiled and run again. Therefore, when. Net 2 introduces generics, the. net generic type is invariant.
List <Animal> listAnimals = new List <Dog> (); therefore, compilation fails.
List <Dog> listDogs = new List <Dog> ();
ListDogs. Add (new Animal (); this cannot be compiled .. NET2 avoids some problems, but it also brings about some problems, such as: List <Dog> listDogs = new List <Dog> ();
IEnumerable <Animal> enumAnimals = listDogs. In fact, this programming scenario is very common, and in fact it is type-safe, because through the IEnumerable interface, we can only get values from enumAnimals, rather than assign values to them.
The following is an example of contravariance:
Public class Animal
{
Public int Weight
{
Get; set;
}
Public string Name
{
Get; set;
}
Public Animal (string name, int weight)
{
Name = name; Weight = weight;
}
}
Public class Dog: Animal
{
Public Dog (string name, int weight): base (name, weight)
{
}
}
Public class WeightComparer: IComparer <Animal>
{
Public int Compare (Animal x, Animal y)
{
Return x. Weight-y. Weight;
}
} Adds a weight attribute to the animal class and implements an IComparer class sorted by weight.
Class Program
{
Static void Main (string [] args)
{
WeightComparer comparer = new WeightComparer ();
List <Animal> animals = new List <Animal> ();
Animals. Add (new Animal ("Dog", 4 ));
Animals. Add (new Animal ("Mouse", 1 ));
Animals. Add (new Animal ("Tiger", 44 ));
Animals. Sort (comparer); // works fine
Animals. ForEach (e => Console. WriteLine (e. Name + "" + e. Weight ));
List <Dog> dogs = new List <Dog> ();
Dogs. Add (new Dog ("DogA", 12 ));
Dogs. Add (new Dog ("DogB", 10 ));
Dogs. Sort (comparer); // compile error
Dogs. ForEach (e => Console. WriteLine (e. Name + "" + e. Weight ));
}
} Note that if the program runs in. net 2, the first program can run normally. The second program will cause a compilation error. Here, the object should be IComparer <Dog>, but it is actually passed to ICompaer <Animal> and contravariance, which is in. net 2 is not allowed. In fact, this situation is also type-safe.
In. Net 4, you can add the in or out keyword before the generic parameter. The in keyword can change the parameter to contravariant. The out keyword can change the parameter to covariant. In the. net class library, many generic interfaces and delegate declarations have changed. For example, the out keyword is:
IEnumerable (Of T), IEnumerator (Of T), IQueryable (Of T) IGrouping (Of TKey, TElement)
The keyword in is:
IComparer (Of T), IComparable (Of T), IEqualityComparer (Of T ).
Therefore, the Code involved in the generic interface above can be successfully run in. net4.
Some common delegation in. Net4 also uses the in and out keywords to declare generic parameters. For example:
The Action <in T> delegate is declared:
Public delegate void Action <in T> (T obj)
You can use contravariance:
Static void Main (string [] args)
{
Action <Animal> animal = (obj) => {Console. WriteLine (obj. GetType (). ToString ());};
Action <Dog> dog = animal;
Dog (new Dog ("Test", 1 ));
} For another example, the Func <T, TResult> delegate is declared:
Public delegate TResult Func <in T, out TResult> (T arg)
The input parameter can be contravariant, And the return value can be covariant.
Public class Type1 {}
Public class Type2: Type1 {}
Public class Type3: Type2 {}
Public class Program
{
Public static Type3 MyMethod (Type1 t)
{
Return t as Type3 ?? New Type3 ();
}
Static void Main ()
{
Func <Type2, Type2> f1 = MyMethod;
// Covariant return type and contravariant parameter type.
Func <Type3, Type1> f2 = f1;
Type1 t1 = f2 (new Type3 ());
Console. WriteLine (t1.GetType ());
}
}
The running result is Type3.