C#4 and VB10, unveiled with Visual Studio CTP, have gone a pretty different way in supporting the new features of the language: C # focuses on adding late binding and several features that are compatible with dynamic languages, VB10 focus on simplifying language and improving abstraction ; But both add a function: covariant (covariant) and inverse (contravariant) of the generic type. Many people are aware that they may be limited to the addition of the In/out keyword, and are unaware of its many features. Here are some detailed explanations to help you use this feature correctly.
Background knowledge: Covariance and inversion
Many people may not be able to understand these nouns from physics and mathematics very well. We don't need to understand their mathematical definitions, but at least we should be able to distinguish between covariance and inversion. The word actually derives from the binding between the type and the type. We start with an array of understanding. An array is actually a type of binding that occurs between a specific type. The array type int32[] corresponds to the original type of Int32. Any type T has its corresponding array type t[]. So our problem is, if there is a safe implicit conversion between the two types T and U, is there such a conversion between the corresponding array type t[] and u[]? This involves the ability to map type conversions that exist on the original type to their array types, which is called "variability (Variance)." In the. NET world, the only type conversion that allows for variability is the " subclass reference -and Parent-class reference " transformation brought by an inheritance relationship. For example, a string type inherits from the type of object, so any reference to a string can be safely converted to an object reference. We found that the reference to the string[] array type also inherits this conversion capability, which can be converted to a reference of the object[] array type, which is called Writers Association (covariant) with the same variability as the original type conversion direction. .
Since arrays do not support inversion, we cannot use examples of arrays to explain the inverse, so let's look at the variability of generic interfaces and generic delegates. Suppose there are two types: Tsub is a subclass of Tparent, and it is obvious that tsub references can be safely converted to tparent type references. If a generic interface ifoo<t>,ifoo<tsub> can be converted to ifoo<tparent>, we call this process covariant and say that the generic interface supports the T - the covariance . And if a generic interface ibar<t>,ibar<tparent> can be converted to t<tsub>, we call this process the inverse (contravariant) , and says that this interface supports the inverse of T . Therefore, it is well understood that if a variability and subclass are in the same direction as the parent class, it is called Writers Association, and if the transition direction from the subclass to the parent class is reversed, it is known as an inverse degeneration. Do you remember?
Generic covariance, inversion introduced by. NET 4.0
We have already used the covariant and inverse of the generic interface when we were explaining the concept, but before. NET 4.0, the variability of generics is not supported in either C # or VB. However, they all support covariance and inversion of the delegate parameter type. Because the variability of the delegate parameter types is understood to be highly abstract, we are not going to discuss them here. Readers who have been able to understand these concepts themselves must be able to understand the variability of the delegate parameter types themselves. Why not allow ifoo<t> to be covariant or reversed before. NET 4.0? Because of the interface, the type parameter of T can be used for both method parameters and method return values. Imagine an interface like this
interface IFoo (of t) sub method1 (byval param as t) &NBSP;&NBSP;&NBSP;FUNCTION&NBSP;METHOD2 () as t End interface |
Interface ifoo<t> { void method1 (T param ); T Method2 (); } |
If we allow covariance, from ifoo<tsub> to ifoo<tparent> conversion, then IFOO.METHOD1 (tsub) becomes ifoo.method1 (tparent). We all know that tparent is not safe to convert into tsub, so Method1 this method becomes unsafe. Similarly, if we allow the inversion to ifoo<tparent> to Ifoo<tsub>, then the Tparent ifoo.method2 () method becomes Tsub ifoo.method2 (), The originally returned tparent reference may not be able to be converted to a tsub reference, and the METHOD2 call will be unsafe. It can be seen that, with no additional mechanism constraints, the interface is not type-safe for covariance or inversion. What does the. NET 4.0 improve? It allows an additional description to be added to the declaration of a type parameter to determine the scope of use of the type parameter. We see that if a type parameter can only be used for the return value of a function, then the type parameter is compatible with the covariance. Instead, a type parameter is compatible with the inverse if it can only be used for method parameters. As shown below:
interface ICo (of out t) function method () as t End Interface Interface icontra (of in t) Sub Method (byval param as t) End interface |
Interface ico<out t> { T Method () ; } Interface icontra<in t> { void method (T param); } |
You can see that both c#4 and VB10 provide a similar syntax, with out to describe only type parameters that can be returned as values, and in to describe type parameters that can only be used as method parameters. An interface can take more than one type parameter, which can be both in and out, so we cannot simply say that an interface supports covariance or inversion, only that an interface supports covariance or inversion for a specific type parameter. For example, if there is an interface such as Ibar<in T1, out t2>, it will support the T1 to support the change to T2. For example, Ibar<object, string> can be converted into ibar<string, Object>, where there is both covariance and inversion.
In the. NET framework, many interfaces only use type parameters for parameters or return values. For ease of use, these interfaces are re-declared in the. NET Framework 4.0 as versions that allow covariance or inversion. For example, icomparable<t> can be re-declared as Icomparable<in T>, while ienumerable<t> can be re-declared as Ienumerable<out T>. However, some interface ilist<t> cannot be declared in or out, and therefore cannot support covariance or inversion.
Here are a few caveats that are easy to ignore for generic covariance and inversion:
1. Only generic interfaces and generic delegates support the variability of type parameters, which are not supported by generic classes or generic methods.
2. Value types that do not participate in covariant or inverse,ifoo<int> can never be converted to IFOO<OBJECT>, regardless of whether or not the declaration is out. Because. NET generics, each value type generates a proprietary enclosing constructed type that is incompatible with the reference type version.
3. When declaring attributes, be aware that read-write properties use the type for both parameters and return values. Therefore, only read-only properties allow out type parameters, and write-only properties can use the in parameter.
The interaction of covariant and trans-change
This is a rather interesting topic, let's take a look at an example:
Interface IFoo (of In T) End Interface Interface IBar (of In T) Sub Test (ByVal foo as IFoo (of T)) ' right? End Interface |
Interface Ifoo<in t> { } Interface Ibar<in t> { void Test (ifoo<t> foo); Is that right? } |
Can you tell what's wrong with the above code? I declare in T, and then use it for the parameters of the method, everything is OK. But to your surprise, this code is not compiled through! Instead, the code is compiled by:
interface IFoo (of in t) End interface Interface ibar (of out t) sub test (Byval foo as ifoo (Of T)) End interface |
Interface ifoo<in t> { /p> } Interface ibar<out t> { void test (ifoo<t> foo); } |
What the? Is it an out parameter, but we have to use it for the parameter of the method to be valid? At first glance there will be some surprises. We need to take some trouble to understand the problem. Now we consider ibar<string> it should be able to co-ibar<object> Because string is a subclass of object. So Ibar.test (ifoo<string>) also became Ibar.test (ifoo<object>). When we call this covariant method, we will pass in a ifoo<object> as a parameter. Think of this method is from Ibar.test (ifoo<string>) covariant, so the parameter ifoo<object> must be able to become ifoo<string> to meet the needs of the original function. The requirement for ifoo<object> here is that it can be reversed into ifoo<string>!. Rather than covariant. That is, if an interface requires a T covariance, then the parameter types of all methods of this interface must support the inverse of t. Similarly, we can see that if the interface is to support the T -Change, then the parameter types of the methods in the interface must support the T covariance line. This is the covariance of method parameters - The principle of trans-change swaps . Therefore, we can not simply say that the out parameter can only be used for the return value, it can only be used directly to declare the return value type, but as long as a type assistance that supports the inversion, the out type parameter can also be used for the parameter type! In other words, the in parameter, in addition to declaring the method arguments directly, can only be used for method parameters with a type that supports covariant, and it is not allowed to support only the type of the T-variable as a method parameter. To understand this concept in a deep sense, it may be a bit of a detour for the first time, and it is advisable to do more experiments with conditional conditions.
The mutual effects of covariance and inversion on the method parameters are mentioned just now. Will the return value of the method have the same problem? Let's look at the following code:
interface Ifooco (of out t) End interface Interface ifoocontra ( of in t) End interface Interface ibar (of out &NBSP;T1,&NBSP;IN&NBSP;T2) function test1 () as ifooco (Of T1) function test2 () as ifoocontra (of t2) End Interface |
Interface ifooco<out t> { } Interface ifoocontra<in t> { } Interface ibar<out t1, in t2> { IFooCo<T1> Test1 (); IFooContra<T2> Test2 (); } |
We see exactly the opposite, if an interface needs to be covariant or reversed for T, then the return value type of all methods of this interface must support covariance or inversion in the same direction as T. This is the covariance of the method return value - The principle of the change of conformity . That is, even if the in parameter can be used for the return value type of a method, it is possible to use a type that can be reversed as a bridge. If this process is not particularly clear, the recommendation is to write some code to do the experiment. So far we have found that covariance and inversion have many interesting features so that in and out of the code are not as well understood as they literally mean. When you see the in parameter appears in the return value type, the Out parameter appears in the parameter type, do not faint, with the knowledge of this article can be cracked one of the mysteries.
Summarize
After the explanation of this article, we should have a preliminary understanding of the meaning of covariance and inversion, can distinguish the process of co-change and inverse change. We also discussed the new features and new syntax for. NET 4.0 support for generic interfaces, covariance and inversion of delegates. Finally, we also set up the theory of covariance, inversion and function parameters, the reciprocal function of the return value, and the wonderful way of writing. I hope that after reading my article, I can apply this knowledge to the design of generic programs and use the new features of. NET 4.0 correctly. I wish you a happy use!
Generic covariance and Contravariance in. NET 4.0