To sum up one sentence: the covariant allows a coarse-grained interface (or delegate) to receive a more specific interface (or delegate) as a parameter (or return value); the inverter allows an interface (or delegate) the parameter type (or return value) of is more specific, that is, the parameter type is stronger and clearer.
Usually, the covariant type parameter can be used as the delegate return type, while the inverter type parameter can be used as the parameter type. For interfaces, the covariant type parameters can be used as the return type of the interface method, while the inverter type parameters can be used as the parameter type of the interface method.
Covariant
Let's take a look at the following example from MSDN:
01 // covariant
02 IEnumerable <string> strings = new List <string> ();
03 IEnumerable <object> objects = strings;
04 // you can see that the API type declared as IEnumerable <string> is assigned to a lower-level IEnumerable <object>.
05 // correct. This is the covariant. Let's look at an example:
06 class Base
07 {
08 public static void PrintBases (IEnumerable <Base> bases)
09 {
10 foreach (Base B in bases)
11 {
12 Console. WriteLine (B );
13}
14
15}
16}
17
18 class Derived: Base
19 {
20 public static void Main ()
21 {
22 List <Derived> dlist = new List <Derived> ();
23 Derived. PrintBases (dlist );
24 // because the IEnumerable <T> interface is covariant, PrintBases (IEnumerable <Base> bases)
25 // you can receive a more specific IEnumerable <Derived> as its parameter.
26 IEnumerable <Base> bIEnum = dlist;
27}
28}
The following is the definition of the covariant:
Covariant: allows a generic interface (or delegate) with covariant parameters to receive more refined types, and the specific generic interface (or delegate) is used as the parameter, it can be seen as an extension of polymorphism in OO.
Inverter
1 // Inverter
2 // Assume that the following method is in the class:
3 // static void SetObject (object o ){}
4 Action <object> actObject = SetObject;
5 Action <string> actString = actObject;
6 // use a more refined string type in the future to delegate actString. No more objects can be used!
7 string strHello ("Hello ");
8 actString (strHello );
Have you seen this? A type declared as Action <object> is assigned to an Action <string>. As we all know, Action <T> receives parameters with no return value, therefore, the object and string are their parameters. In this process, the parameter constraints are enhanced, that is, the parameter types are more refined. Next we will give the inverter a definition:
Inverter: allows a generic interface (or delegate) with covariant parameters to receive more coarse-grained generic interfaces or delegates as parameters. This process is actually a more refined process of parameter types.
I. Two Concepts: strong type and weak type
For the convenience of subsequent descriptions, I now define two concepts: strong type and weak type. In this article, strong and weak types refer to two classes with direct or indirect inheritance relationships. If a class is a direct or indirect base class of another class, it is a weak type, and the direct or indirect subclass is a strong type. The Foo and Bar classes used in subsequent introductions are defined here. Bar inherits from Foo. Foo is a weak type, while Bar is a strong type.
1 public class Foo
2 {
3 // Others Members...
4}
5 public class Bar: Foo
6 {
7 // Others Members...
8}
With the concept of strong and weak types, we can define co-Variation and inversion in this way: if the type of TBar is based on the type of strong Bar (for example, the Type parameter is Bar's generic type, or the parameter/return value type is Bar delegate), while the TFoo type is based on the Foo type of the weak type, the covariant is to assign the TBar type instance to the variable of the TFoo type, the inverter assigns a TFoo-type instance to a variable of the TBar type.
Ii. Use of coordination and inverter in Delegation
The coordination and inverter are mainly reflected in two aspects: interface and delegation. First, let's take a look at how to use the coordination and inverter in the delegation. Now we have defined a generic delegate Function that represents a Function without parameters. The type parameter is the type of the Function return value. An out keyword is added before the generic parameter to indicate that T is a covariant. During use, the forced-type delegated Fucntion instance can be assigned a value to the weak-type delegated Fucntion variable.
01 public delegate T Function <out T> ();
02 class Program
03 {
04 static void Main ()
05 {
06 Function funcBar = new Function (GetInstance );
07 Function funcFoo = funcBar;
08 Foo foo = funcFoo ();
09}
10 static Bar GetInstance ()
11 {
12 return new Bar ();
13}
14}
Next we will introduce how to use inverter delegation. The following defines a generic delegate named Operate to accept a parameter with a generic parameter type. The in keyword is added before defining generic parameters, indicating that T is an inverter-based variant. Because of the inverter, we can assign a weak-type delegated Operate instance to a strongly-type delegated Operate variable.
01 public delegate void Operate <in T> (T instance );
02 class Program
03 {
04 static void Main ()
05 {
06 Operate opFoo = new Operate (dosomething );
07 Operate opBar = opFoo;
08 opBar (new Bar ());
09}
10 static void dosomething (Foo foo)
11 {
12 // Others...
13}
14}
Iii. Use of covariant and inverter in Interfaces
Next we will also use a simple example to illustrate how to use the covariant and inverter in the interface. The following defines an IGroup set type inherited from the IEnumerable interface. Like above, the out keyword before the generic parameter T indicates that this is a covariant. Since it is a covariant, we can assign a forced type-based delegated IGroup instance to the weak type-based delegated IGroup variable.
01 public interface IGroup <out T>: IEnumerable
02 {}
03
04 public class Group: List, IGroup
05 {}
06
07 public delegate void Operate <in T> (T instance );
08
09 class Program
10 {
11 static void Main ()
12 {
13 IGroup groupOfBar = new Group ();
14 IGroup groupOfFoo = groupOfBar;
15 // Others...
16}
17}
The following is an example of an inverter interface. First, an IPaintable interface is defined, which defines a read/write Color attribute, that is, the object that implements this interface has its own Color and can change the Color. The type Car implements this interface. The interface IBrush defines a brush. The generic type must implement the IPaintable interface. The in keyword indicates that this is an inverter. The method Paint is used to Paint the specified object into the corresponding color, indicating that the type of the object to be painted is generic parameter type. This interface is implemented by the Brush. Because IBrush is defined as an inverter, we can assign a value to the weak type-based delegated IBrush variable.
Public interface IPaintable
{
Color {get; set ;}
}
Public class Car: IPaintable
{
Public 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. view the essence of coordination and inverter from Func
Next, let's talk about the essential differences between covariant and inverter. Here we use a very familiar entrusted Func as an example. The definition of this delegate is given below. We can see that the two generic parameters defined by Func belong to the inverter and the covariant. Specifically, the input parameter type is invert, And the return value type is covariant.
1 public delegate TResult Func <in T, out TResult> (T arg );
Repeat the following sentence: "The input parameter type is inverter, And the return value type is covariant ". Then, you can think about why the in keyword is used for inverter, And the out keyword is used for covariant. These two are not accidental. In fact, we can match the covariant/inverter with the output/input.
From another perspective, we can understand the coordination and inverter. We know that an interface represents a contract. When an interface is implemented by a type, it is equivalent to signing this contract, so it must be all the members of the Implementation interface. In fact, type inheritance also belongs to a contractual relationship. The basic class defines the contract, and the subclass "signs" the contract. For a type system, the interface implementation and type inheritance are essentially the same. The contract type is weak, and the contract type is strong.
Apply the contract idea to the delegate. The delegate actually defines the signature of a method (parameter list and return value). The type of parameters and return values is contract, the key now is who will fulfill the contract. All parameters are input from outside, so the contract operator based on parameters comes from outside, that is, the type of the variable to be assigned, so the type of the variable to be assigned is strongly. For the agent itself, a parameter is an input, that is, an inverter represented by the in keyword.
For the return value of the Delegate, this is for external services and a commitment from the entrusting itself to the outside world. Therefore, it is the fulfillment of the contract itself, so it should be a strong type. Correspondingly, for the proxy itself, the return value is an output, that is, a covariant defined by the out keyword.
Also, for this reason, for a delegate, you cannot define the parameter type as a covariant or convert the return type. The following two variant definitions cannot be compiled.
1 delegate TResult Fucntion <out T, TResult> (T arg );
2 delegate TResult Fucntionin TResult> (T arg );
Speaking of this, I want to ask a question. Since the input represents an inverter and the output represents a covariant, should the output parameter of the delegate be defined as a covariant? Also, the output parameters both output and output here (after all, you need to specify an object of the corresponding type when calling ). For this reason, the type of the output parameter cannot be defined as a covariant or an inverter. Therefore, the definitions of the following two variants cannot be compiled.
1 delegate void Action <in T> (out T arg );
2 delegate void Action <out T> (out T arg );
V. inverter achieves reuse of "algorithms"
In fact, the programming ideology embodied by the relationship covariant and inverter is also a type of concept that I highly recommend: The covariant represents the inheritance, while the inverter represents the polymorphism. In fact, this is essentially the same as the contract relationship analyzed above.
For inverter, let me try again: the programming ideas behind the inverter reflect the reuse of algorithms-we have defined a set of operations for the Base class, it can be automatically applied to all subclass objects.
Complete example
01 /// <summary>
02 // covariant and invert parameters allow implicit reference conversion for array type, delegate type, and generic type parameters. The Coordination variable retains the allocation compatibility, which is opposite to the inverter.
03 // covariant and invert can only be used for reference type, but not for value type or void
04 /// </summary>
05 public class CovarianceAndContravariance: IFace
06 {
07 public CovarianceAndContravariance ()
08 {
09 // allocate compatibility
10 string str = "test ";
11 object obj = str;
12
13 // The covariant of the array allows implicit conversion of arrays of more derived types to arrays of more derived types. However, this operation is not a type-safe operation.
14 object [] array = new String [10];
15 // array [0] = 10;
16
17 // method coordination and Inversion
18 Func <object> del = GetString;
19 // Func <string> del00 = GetObject; // the return value cannot be inverter.
20 Action <string> del2 = SetObject;
21 // Action <object> del22 = SetString; // parameters cannot be changed together.
22 Action <object> actObject = SetObject;
23 Action <string> actString = actObject;
24
25 // implicit conversion of generic parameters
26 IEnumerable <string> strings = new List <string> ();
27 IEnumerable <object> objects = strings;
28}
29
30 static object GetObject () {return null ;}
31 static void SetObject (object obj ){}
32
33 static string GetString () {return "";}
34 static void SetString (string str ){}
35
36 /// <summary>
37 // The API does not have any covariant or inverter.
38 /// </summary>
39 // <param name = "obj"> </param>
40 /// <returns> </returns>
41 public string func (object obj)
42 {
43 return null;
44}
45 public object func2 (string obj)
46 {
47 return null;
48}
49}
50 public interface IFace
51 {
52 string func (object obj );
53 object func2 (string obj );
54}
To sum up one sentence: the covariant allows a coarse-grained interface (or delegate) to receive a more specific interface (or delegate) as a parameter (or return value); the inverter allows an interface (or delegate) the parameter type (or return value) of is more specific, that is, the parameter type is stronger and clearer.
Usually, the covariant type parameter can be used as the delegate return type, while the inverter type parameter can be used as the parameter type. For interfaces, the covariant type parameters can be used as the return type of the interface method, while the inverter type parameters can be used as the parameter type of the interface method.