In a nutshell: Covariance allows a coarse-grained interface (or delegate) to receive a more specific interface (or delegate) as a parameter (or a return value), and the inverse allows the type of the parameter (or return value) of an interface (or delegate) to be more concrete, that is, the parameter type is stronger and clearer.
Typically, covariant type parameters can be used as the return type of a delegate, whereas contravariant type parameters can be used as parameter types. For an interface, the covariant type parameter can be used as the return type of the interface's method, whereas the contravariant type parameter can be used as the parameter type of the interface's method.
Co-change
Let's take a look at one of the following examples from MSDN:
01//Co-change
ienumerable<string> strings = new list<string> ();
ienumerable<object> objects = strings;
04//Did you see that? An interface type declared as Ienumerable<string> is assigned to a lower-level ienumerable<object>.
05//Yes, this is covariant. Let's look at one more example:
Class Base
07 {
public static void PrintBases (Ienumerable<base> bases)
09 {
Ten foreach (Base b in bases)
11 {
Console.WriteLine (b);
13}
14
15}
16}
17
Class Derived:base
19 {
public static void Main ()
21 {
list<derived> dlist = new list<derived> ();
Derived.printbases (dlist);
24//Because the Ienumerable<t> interface is covariant, printbases (ienumerable<base> bases)
25//Can receive a more specific ienumerable<derived> as its parameters.
ienumerable<base> bienum = dlist;
27}
28}
The following defines the covariance:
Covariance: A generic interface (or delegate) with covariant parameters can receive a more granular type, and a materialized generic interface (or delegate) as a parameter can be considered an extension of the polymorphism in OO.
Inverse change
1//Inverter
2//Assume that the following method was in the class:
3//static void SetObject (object o) {}
4 action<object> actobject = SetObject;
5 action<string> actstring = Actobject;
6//Delegate actstring later to use a more granular type string no longer uses Object!
7 string Strhello ("Hello");
8 actstring (Strhello);
Did you see that? A type declared as Action<object> is assigned to a action<string>, and everyone knows that,action<t> receives the parameter, there is no return value, so the object and string are its arguments. This process is actually the constraint of the parameter is more strengthened, that is, to make the parameter type more refined. Next we'll define the inverse:
Contravariance: Having a generic interface (or delegate) with covariant parameters can receive a more granular generic interface or delegate as a parameter, which is actually a more granular process of parameter types.
One or two concepts: strong type and weak type
For the convenience of the following narrative, I now customize two concepts: strongly typed and weakly typed. In this article, strong and weak types refer to two of the two classes that have a direct or indirect inheritance relationship. If a class is a direct or indirect base class for another class, it is a weakly typed, direct or indirect subclass that is strongly typed. The two classes Foo and bar that will be used in subsequent introductions are defined here. Bar inherits from Foo. Foo is a weak type, while bar is strongly typed.
1 public class Foo
2 {
3//others Members ...
4}
5 public class Bar:foo
6 {
7//others Members ...
8}
With the concept of strongly typed and weakly typed, we can define covariance and contravariance in this way: if the type Tbar is based on a strongly-typed bar type (such as a generic type with a type parameter of bar, or a delegate with a parameter/return value type of bar), and type Tfoo is based on the type of the weakly-typed Foo, Covariance is the assignment of an instance of the Tbar type to a variable of type Tfoo, whereas contravariance is a variable that assigns an instance of the Tfoo type to a tbar type.
Second, the use of covariance and contravariance in entrustment
Covariance and contravariance are mainly embodied in two places: Interfaces and delegates, first to see how covariance and contravariance are used in a delegate. Now we define the following generic delegate function that represents the parameterless functions, and the type parameter is the type of the function return value. A generic parameter was previously added with an out keyword that indicates that T is a covariant variant. Then in the process of use, the Fucntion instance based on the strongly typed delegate can be assigned to the Fucntion variable based on the weakly typed delegate.
Delegate T Function<out t> ();
The class program
03 {
static void Main ()
05 {
function Funcbar = new function (getinstance);
-Function Funcfoo = Funcbar;
Foo foo = Funcfoo ();
09}
Ten static Bar getinstance ()
11 {
Return to New Bar ();
13}
14}
Next, we introduce the usage of the contravariant delegate. The following defines a generic delegate named operate that accepts a parameter with a generic parameter type. The In keyword was added before the generic parameter was defined, indicating that T is a variant based on contravariance. Due to the use of contravariance, we can assign a operate instance based on a weakly typed delegate to a strongly typed delegate operate variable.
delegate void Operate<in t> (T instance);
The class program
03 {
static void Main ()
05 {
Operate Opfoo = new Operate (DOSTH);
Operate Opbar = Opfoo;
Opbar (New Bar ());
09}
Ten static void Dosth (foo foo)
11 {
//others ...
13}
14}
Third, the use of covariance and contravariance in the interface
Next we also use a simple example to illustrate how covariance and contravariance are used in interfaces. The following defines a Igroup collection type that inherits from the IEnumerable interface, and as above, the OUT keyword before the generic parameter T indicates that this is a covariant. Since it is covariant, we can assign a Igroup instance based on a strongly typed delegate to the Igroup variable based on the weakly typed delegate.
Interface Igroup<out t>: IEnumerable
02 {}
03
public class Group:list, Igroup
05 {}
06
delegate void Operate<in t> (T instance);
08
The class program
10 {
One-to-one static void Main ()
12 {
Igroup Groupofbar = new Group ();
Igroup Groupoffoo = Groupofbar;
//others ...
16}
17}
The following is an example of an inverter interface. The first defines a ipaintable interface, which defines a read-write color attribute, which is the object of the type that implements the interface has its own color and can change color. Type car implements the interface. Interface Ibrush defines a brush, the generic type needs to implement the Ipaintable interface, and the IN keyword indicates that this is an inversion. The method paint is used to paint the specified object as a corresponding color, indicating that the type of the object being painted is a generic parameter type. The brush implements the interface. Since Ibrush is defined as contravariant, we can assign a strongly typed delegate Ibrush instance to a delegate Ibrush variable based on a weak type.
public interface Ipaintable
{
Color color {get; set;}
}
public class Car:ipaintable
{
Public color color {get; set;}
}
public interface Ibrush<in t> where t:ipaintable
{
void Paint (T objecttopaint, color color);
}
public class Brush:ibrush where t:ipaintable
{
public void Paint (T objecttopaint, color color)
{
Objecttopaint.color = Color;
}
}
Class Program
{
static void Main ()
{
Ibrush brush = new Brush ();
Ibrush Carbrush = brush;
Car car = new car ();
Carbrush.paint (car, color.red);
Console.WriteLine (car. Color.name);
}
} iv. the nature of covariance and contravariance from the perspective of Func
Let's talk about what the essential difference between covariance and contravariance is. Here we take the example of a delegated func that we are very familiar with, and the definition of the delegate is given below. We can see that the two generic parameters defined by Func belong to Contravariance and covariance, respectively. Specifically, the input parameter type is contravariant, and the return value type is covariant.
1 public delegate TResult Func<in T, out tresult> (t arg);
Reiterate the following sentence "input parameter type is contravariant, return value type is covariant". Then, you think about why contravariance uses the In keyword, and covariance uses the Out keyword. These two are not accidental, we can actually match the covariance/contravariance with the output/input.
We understand covariance and contravariance from another angle. We know that the interface represents a contract, and when a type implements an interface it is equivalent to signing the contract, so it must be the implementation of all the members of the interface. In fact, type inheritance also belongs to a contractual relationship, the base class defines the contract, and the subclass "signs" the contract. For type systems, interface implementations and type inheritance are inherently consistent. A contract is a weak type, and it is a strong type to sign the contract.
By applying the contract's view to the delegate, the delegate actually defines the signature of a method (the argument list and the return value), then the type of the parameter and return value is the contract, and the key now is who is going to fulfill the contract. All parameters are imported, so the parameter-based contract performers originate from the outside, that is, the type of the assigned variable, so the type of the assigned variable is strongly typed. For the agent itself, the parameter is an input, which is a contravariant represented by the in keyword.
And for the return value of the delegate, this is to the external service, is entrusted itself to the outside world a commitment, so it is the contract to fulfill itself, so it should be a strong type. Accordingly, for the agent itself, the return value is an output, which is a covariant defined by the Out keyword.
Also formally for this reason, for a delegate, you cannot define the parameter type as covariant, nor can you define the return type as contravariant. The variations defined in the following two methods are not compiled.
1 delegate TResult Fucntion<out T, tresult> (t arg);
2 delegate TResult Fucntionin tresult> (T Arg);
Speaking of which, I think someone has to ask a question, since the input represents the inverse, the output represents the covariance, the output parameters of the delegate should be defined as covariant? Non-also, the output parameters are actually output here, and also output input (after all, the call needs to specify a corresponding type of object). It is for this reason that the type of the output parameter and cannot be defined as covariant, nor can it be defined as contravariant. So the definition of the following two variants is not compiled either.
1 delegate void Action<in t> (out T Arg);
2 delegate void Action<out t> (out T Arg);
V. Inversion realizes the reuse of "algorithm"
In fact, the relationship between covariance and inversion of the programming idea, there is a kind of I would like to say, that is: covariance is the embodiment of inheritance, and the inverse of the embodiment is polymorphic. This is in fact consistent with the contractual relationship analyzed above.
About contravariance, let me say one more thing: The programming thought behind the inversion embodies the reuse of the algorithm--we define a set of operations for the base class that can be automatically applied to objects of all subclasses.
Complete example
<summary>//
02///covariant and contravariant allow implicit reference conversions for array types, delegate types, and generic type parameters. Covariance preserves assignment compatibility, which is reversed.
03///covariant and contravariant can only be used for reference types, not for value types or void
</summary>
public class Covarianceandcontravariance:iface
06 {
Public Covarianceandcontravariance ()
08 {
09///Assignment compatibility
Ten string str = "Test";
One object obj = str;
12
13////The covariance of arrays allows an array of more derived types to be implicitly converted to an array of less derived types, but this operation is not a type-safe operation.
object[] array = new STRING[10];
//array[0] = 10;
16
17///covariance and contravariance of methods
Func<object> del = GetString;
//func<string> del00 = GetObject; The return value cannot be reversed
action<string> Del2 = setobject;
//action<object> del22 = SetString; Parameter cannot be covariant
action<object> actobject = setobject;
action<string> actstring = Actobject;
24
25//generic type parameter for implicit reference conversions
ienumerable<string> strings = new list<string> ();
ienumerable<object> objects = strings;
28}
29
The static object GetObject () {return null;}
The static void SetObject (Object obj) {}
32
The static string GetString () {return "";}
SetString static void (string str) {}
35
<summary>
37///interface does not exist covariant and contravariant
</summary>
//<param name= "obj" ></param>
<returns></returns>
The public string func (Object obj)
42 {
The return null;
44}
public object Func2 (String obj)
46 {
return null;
48}
49}
public interface IFace
51 {
A. String func (object obj);
Func2 object (String obj);
54}
In a nutshell: Covariance allows a coarse-grained interface (or delegate) to receive a more specific interface (or delegate) as a parameter (or a return value), and the inverse allows the type of the parameter (or return value) of an interface (or delegate) to be more concrete, that is, the parameter type is stronger and clearer.
Typically, covariant type parameters can be used as the return type of a delegate, whereas contravariant type parameters can be used as parameter types. For an interface, the covariant type parameter can be used as the return type of the interface's method, whereas the contravariant type parameter can be used as the parameter type of the interface's method.
c#4.0 new Features (3): Denaturing Variance (contravariance and covariance)