Ten minutes may be suspected of winning favor. I wrote this blog and talked about it for half a day. I checked a lot of information to complete it. Therefore, it may take a little longer to understand the essence of covariant inverter.
--------------------------------------------------------------------------------
In many articles, the change description is roughly as follows:
A type with a high degree of refinement is assigned to a type with a low degree of refinement. For example, if the return value of M is Giraffe (Giraffe), you can assign the return value of M to the Animal (Animal) type, because the Animal type is the type at the bottom of the degree of refinement, compatible with the Giraffe class. Then the method is covariant, because when you create a covariant interface, you need to use the keyword out, and the return value is out from the method.
This is not the meaning of covariant at all. This only describes the compatibility of values. The two types are compatible.
--------------------------------------------------------------------------------
What is the exact definition of covariant?
We do not consider the type first. We consider the integer in the mathematical sense. Consider the relationship between integers smaller than or equal. Here, the relationship is actually a method that accepts two numbers and returns a Boolean value.
Now let's consider the projection of integers. projection refers to a function that accepts an integer and returns another integer. For example, we can define z → z + z as D (double), z → 0-z as N for (negate), z → z * z, defined as S (square ).
The problem arises. Is it true that (x ≤ y) = (D (x) ≤ D (y) in all circumstances ))? In fact, if x is less than or equal to y, then 2 times of x is less than or equal to 2 times of y. Projection D retains the direction of the non-equal sign.
For N, obviously, 1 ≤ 2 but-1 ≥-2. That is, (x ≤ y) = (N (y) ≤ N (x), projection N reverses the direction of the inequality.
For S,-1 ≤ 0, S (0) ≤ S (-1), but 1 ≤ 2, S (2) ≥ S (1 ). It can be seen that projection S does not retain the non-equal sign direction, and does not reverse the non-equal sign direction.
Projection D is a covariant, which retains the order relationships on integers. Projection N is an inverse type, which reverses the order relationship of integers. Projection S is not the same, so it remains unchanged.
Therefore, it is clear that integers are not variants and smaller than links are not variants. Projection is a covariant or inverter-an integer is accepted to generate a new integer.
--------------------------------------------------------------------------------
Now let's look at the type. Replace the less than relation on the integer. We also have a less than relation on the reference type. If the value of reference type X can be stored on Type Y, a reference type X is less than or equal to the Reference Type Y.
Consider projection of the type. Assume that a projection converts T to IEnumerable <T>. That is, if a parameter of the Giraffe type is accepted, a new type of IEnumerable <Giraffe> is returned. Is this projection covariant in C #4.0? Yes, it retains the direction of order. Giraffe can be assigned to Animal, so the Giraffes sequence can also be assigned to IEnumerable <Giraffe>.
In essence, for A projection, if A can be assigned to B and the projected value a' can be assigned to B ', then it can be said that the projection is A covariant.
We can consider the accept type T to generate the IEnumerable <T> as a projection and call it "IEnumerable <T> ". Therefore, according to the context, when we say that IEnumerable <T> is covariant, it means that the projection of the type T generated IEnumerable <T> is a covariant projection. Because IEnumerable <T> only has one type parameter, it is clear that the parameter we are talking about is T.
Therefore, we can define covariant, invert, and unchanged. If a generic type I <T> retains the compatibility direction of the value assignment based on the Structure Obtained by the type parameter, the generic type is changed collaboratively. That is, if A generic type I <T>, for types A and B, if A can be assigned to B, and I <A> can also be assigned to I <B>, that is, the compatibility direction of the value assignment is reserved. Then, the I <T> generic class is the covariant. On the contrary, the inverter reverses the compatibility of the value assignment. No change means neither the covariant nor the inverter. Simply and accurately, a projection that accepts T and generates I <T> is a co-variant/inverter/constant projection.
--------------------------------------------------------------------------------
In C #, the covariant and inverter 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.
Arrays support covariant, string is compatible with objects, and string is still compatible with objects after array. However, changing the type may result in type insecurity, as shown in the following example:
Object [] array = new String [10];
// Here an error is reported. array [0] has been allocated to the string first, and the integer type cannot be accepted. // Array [0] = 10;
The delegation type also supports covariant. The following code: string is compatible with objects, and fun with the return value is compatible with delegation with the return value.
Public delegate object mydelege ();
Static string fun2 ()
{
Return "";
}
Static void Main (string [] args)
{
Mydelege md1 = fun2; // string is compatible with objects, and fun with the return value of string is compatible with delegation with the return value of object
}
For generic interfaces, covariant is not supported before Framework4.0. For example, IEnumerable <object> B = new List <string> (); cannot be compiled. String is compatible with objects, but List <string> is not compatible with IEnumerable <object>. However, after Framework4.0, the above example can be compiled, that is, the support for IEnumerable <object> generic interfaces has changed. There are many interfaces that support covariant after Framework4.0. IEnumerable <T>, IEnumerator <T>, IQueryable <T>, and IGrouping <TKey, TElement>
The delegate type supports inverter. The following code:
Son inherited father
Class Father
{}
Class Son: Father
{}
Class Program
{
Public delegate void mydelege1 (Father f );
Public delegate void mydelege2 (Son s );
Static void fun1 (Father s)
{
}
Static void fun2 (Son s)
{
}
Static void Main (string [] args)
{
Father f = new Father ();
Son s = new Son ();
F = s; // OK. The son can assign a value to the father.
Mydelege1 md1 = fun2; // error. The input parameter does not support covariant. The son-type method cannot be assigned to the parent-type delegate mydelege2 md2 = fun1; // OK, the father-type method can be assigned to the Son-type delegate.
}
For a delegate mydelege1 (Father f), the input parameter type is defined as Father.
We can see that son can be assigned to father. After the projection is defined by the delegate, it is found that the method of son type as a parameter cannot be assigned to the delegate of father type as a parameter. That is, after such delegated projection, the son type method cannot be assigned to the father type delegate.
On the contrary, methods with the father type as parameters can be assigned to the son type as the parameter delegate. That is, after such a delegated projection, the father type method can be assigned to the son type delegate. This is against the day, so it is the inverter.
Simply put, Son can be assigned to Fahter before projection, and Father can be assigned to Son after projection (converted to delegate type), which is an inverter.
Why can't a Son-type method be assigned to a Father-type delegate after it is converted to a delegate? It is very simple. What this method should accept is that the Son-type method must process the Son-type value, the Father parameter delegate may accept parameters of the Daughter type. (assume that both the Daughter and Son inherit the Father parameter ).
Therefore, the Son method cannot be processed. Therefore, this operation is not allowed. For the code, assume that the code is legal:
Mydelege1 md1 = fun2; // assume it is legal,
The md1 (new Daughter () Code cannot be processed.
Therefore, such a covariant is not allowed, but only the inverter is allowed. Through some examples above, we can see that for the delegate, the covariant only exists in and the return value, and the inverter only exists in and the input value.
--------------------------------------------------------------------------------
Let's look at the co-Variation and invert in generic delegation. We can look at the customized generic delegation, Func, and Action. (Func must have a return type, and Action returns void.) In Framework3.5, Func AND Action do not support covariant or invert conversion. The following code:
Class Father
{}
Class Son: Father
{}
Class Program
{
Static void Main (string [] args)
{
Func <Father> fatherfun = () => new Father ();
Func <Son> sonfun = () => new Son ();
Fatherfun = sonfun; // The type "System. Func <ConsoleApplication2.Son>" cannot be implicitly converted to "System. Func <ConsoleApplication2.Father>
Sonfun = fatherfun; // The type "System. Func <ConsoleApplication2.Father>" cannot be implicitly converted to "System. Func <ConsoleApplication2.Son> "}
}
The above Code cannot be compiled. Therefore, it is inconvenient. Therefore, after Framework4.0, it is allowed to perform covariant and inverter operations. In Framework4.0
Static void Main (string [] args)
{
Func <Father> fatherfun = () => new Father ();
Func <Son> sonfun = () => new Son ();
Fatherfun = sonfun; // OK: The covariant is successful. Son can be assigned to Father and Sonfun can also be assigned to Fatherfun;
Sonfun = fatherfun; // The type "System. Func <ConsoleApplication2.Father>" cannot be implicitly converted to "System. Func <ConsoleApplication2.Son> ". There is an explicit conversion (is forced conversion missing ?)
}
For the inverter, the following code is the same. In the Framework3.5 era, the inverter still cannot be changed collaboratively.
Class Father
{}
Class Son: Father
{}
Class Program
{
Static void Main (string [] args)
{
Action <Father> fatherfun;
Action <Son> sonfun;
Fatherfun = sonfun; // The type "System. Action <ConsoleApplication2.Son>" cannot be implicitly converted to "System. Action <leleapplication2.father>
Sonfun = fatherfun; // The type "System. Action <ConsoleApplication2.Father>" cannot be implicitly converted to "System. Action <leleapplication2.son>"
}
}
After Framework4.0, it is allowed to be inverter.
Static void Main (string [] args)
{
Action <Father> fatherfun;
Action <Son> sonfun;
Fatherfun = sonfun; // The type "System. Action <ConsoleApplication2.Son>" cannot be implicitly converted to "System. Action <leleapplication2.father> ". There is an explicit conversion (is forced conversion missing ?)
Sonfun = fatherfun; // OK: inverter successful
}
The above is the generic delegation provided by Microsoft. In fact, you can also define your own generic delegation, or you can know whether the delegation supports covariant or inverter. In C #, the in and out parameters are used to indicate whether the parameters are inverter parameters or covariant parameters.
--------------------------------------------------------------------------------
Definition in Microsoft MSDN.
The out keyword (Out keyword in Visual Basic and + in MSIL assembler) is used to mark the covariant type parameters. The covariant type parameter can be used as the return value of the method that belongs to the interface, or as the return type of the delegate. However, the covariant type parameter cannot be used as a generic type constraint for interface methods. Parameters of the inverter type are marked with the in keyword (In keyword in Visual Basic and-In MSIL assembler. You can use parameters of the inverter type as the parameter type of the method of the interface, or as the parameter type of the delegate. You can also use the inverter type parameter as the generic type constraint of the interface method. In fact, the definitions of Action and Func are as follows in Framework4.0:
Public delegate void Action <in T> (
T obj)
Public delegate TResult Func <in T, out TResult> (
T arg)
From the definition, we can see that the covariant out is mainly used in the return value, and the inverter in is used in the input parameter. If the output parameter is input, compilation fails. The cause has been explained before. Here we will explain: If we use out for input parameters, and assume that the compilation is successful, the purpose of this operation is to make the parameter change collaboratively. Therefore, we assume that the following code can be compiled successfully.
Class Father
{}
Class Son: Father
{}
Class Daught: Father
{}
Class Program
{
Public delegate void myAction <out T> (T t); // The delegate is similar to the Action <in T>, which means an in and an out, assuming that the code is compiled successfully.
Static void f_father (Father f)
{}
Static void f_son (Son f)
{}
Static void Main (string [] args)
{
MyAction <Father> fatheract = f_father;
MyAction <Son> sonact = f_son;
Fatheract = sonact; // assuming that the above delegate can be compiled successfully, covariant is supported. Therefore, this code can also be set up.
Fatheract (new Daught () // The Code cannot run accurately.
// If this code is valid,
// When fatheract (new Daught () runs
// F_son accepts the Daughter type and cannot run.
}
}
Similarly, if in is used to modify the output type, that is, it is allowed to be inverter, and similar type problems will occur.
Class Father
{}
Class Son: Father
{}
Class Daught: Father
{}
Class Program
{
Public delegate T myFunc <in T> (); // suppose the code can be compiled successfully. That is, assume that the inverter is supported.
Static void Main (string [] args)
{
MyFunc <Father> fatherfunc = () => new Father ();
MyFunc <Son> sonfunc = () => new Son ();;
Sonfunc = fatherfunc; // assume that the above Code is compiled successfully and supports inverter. That is, if fatherfunc is assigned to sonfunc, the following code will cause an exception.
Son ason = sonfunc (); // ason is forced to accept the father type, resulting in exceptions
}
} Therefore, the in corresponds to an inverter and can only be used for output parameters. The out corresponds to a covariant and can only be used for output parameters. Otherwise, problems may occur.
Therefore, allowed input type co-variation in generic delegation will cause type problems, so only inverter is allowed.
Based on the above description, many people may wonder why the in or out annotation should be displayed. the compiler can infer whether generic parameters are covariant or inverter. In fact, the compiler can automatically infer, but the C # team thinks that you need to clearly define a contract and follow it. For example, if the compiler determines for you that a generic type parameter is invert, But you add a member to the interface and use the out mark. This may cause some type errors later. Therefore, the compiler requires you to declare generic type parameters at the beginning. If you do not follow the rules you have defined, the compiler will report an error indicating that you have violated the contract you have defined.
Author: cnn237111