Variation has always been too difficult a topic for me to understand. I recently gave a
talk about new features of C # 4.0 and the variation was one of the topics covered by me. I would like to start with a common script that I have always found hard to understand. Take this code snippet:
IList< object > stuff = new List < string >(); * This source code was highlighted with Source Code Highlighter .
IList< object > stuff = new List < string >(); * This source code was highlighted with Source Code Highlighter .
IList< object > stuff = new List < string >(); * This source code was highlighted with Source Code Highlighter .
This will not compile in any of the existing versions of the .NET Framework. I have always tried to understand why this does not work. The System.String type, of course, satisfies all the requirements of the System.Object. So why is this not working? A generalization of a List is a reference type, which means that every time someone refers to an object of this type in code, he will receive a pointer to some place on the heap. And no matter how many times you use a List, you always get the same link because This is a reference type. So, let's develop our previous example:
- IList < object > stuff = new List < string > ();
- stuff.Add ( "Joe is awesome" );
- stuff.Add (9);
- stuff.Add ( false );
* This source code was highlighted with Source Code Highlighter .
Here is the problem! The variable stuff is IList <object>. So we can add any object to it, including int or bool. But turn back to what we just arranged - the .NET Framework stores a link to the main type declaration, i.e. actually on List <string>. We cannot add int or bool to the list of this type, since these are not string types. This clarifies something that has interested me for so long. Let's examine now this code:
')
- IEnumerable < object > stuff = new List < string > ();
* This source code was highlighted with Source Code Highlighter .
Currently, as explained earlier, .NET does not allow us to do this. Wait, do you think this code should work? Yes, I have to agree. This code should work because IEnumerable is a special type. The fact is that this type does not accept modifications. The only thing IEnumerable is responsible for is returning objects, i.e. it returns objects (objects coming out). This sows some doubts about our previous findings. Here we have an absolutely correct scenario, when you should be able to perform assignments because we will not make any internal modifications and can, therefore, avoid any such typing problems found in the previous example.
Here, IEnumerable <string> can definitely play the role of IEnumerable <object>, because string is the heir from object, which means object is a more general type. In other words, the type of string is covariant to the type of object. The only problem is .NET 3.5 and earlier versions do not have a way to resolve this situation. Switch to .NET 4.0. Here are a few interface definitions:
- public interface IEnumerable < out T>: IEnumerable
- {
- IEnumerator <T> GetEnumerator ();
- }
- public interface IEnumerator < out T>: IEnumerator
- {
- bool MoveNext ();
- T Current { get ; }
- }
* This source code was highlighted with Source Code Highlighter .
The new keyword you see, “out”, determines that we only allow T to be returned from our interface. This will allow our previous attempts to assign IEnumerable <string> to an IEnumerable <object> because we simply tell the compiler that T will never be passed to our interface.
Of course, there is a reverse script when the type is only transferred to your interface. It is better to demonstrate by looking at the following class:
- class program
- {
- void Manipulate ( object obj)
- {
- }
- void ContravariantGoodness ()
- {
- Action < object > manipulateObject = Manipulate;
- Action < string > manipulateString = Manipulate;
- manipulateString = manipulateObject;
- }
- }
* This source code was highlighted with Source Code Highlighter .
The only thing that you don’t see here is that the Action type in C # 4.0 contains the “in” keyword for a generic parameter. As stated earlier, this will not work in .NET 3.5 or earlier versions. In this example, the object type is contravariant to the string type because object refers to string as a more general class to a more specific one. Since in this case the variable is passed as object, we can actually pass anything (almost) into .NET that satisfies this contract, including the type string. Very cool lotion.
It is necessary to remember the main thing that all this is based on reference types. Something like this will never work in any of the scenarios:
- public struct Parent <T>
- {
- public T GetT ()
- {
- ...
- }
- }
* This source code was highlighted with Source Code Highlighter .
I really see it in .NET 4.0 and I personally think that this is the best new feature added. Now go and check it out.