IList<object> stuff = new List<string>();This will not compile in any existing version of the .NET framework. I have always struggled to understand why this wouldn’t work. The System.String type is certainly something that can fulfill all the requirements of the System.Object. Well, here’s why this doesn’t work. The generic List is a reference type. What this means, is that every time somebody references that object in the code, they will get a pointer to some location on the heap. It doesn’t matter how many times you use the List, you always get the same reference to the pointer, hence reference type. So, let’s expand on our previous example:
IList<object> stuff = new List<string>();
stuff.Add("Joe is awesome");
stuff.Add(9);
stuff.Add(false);Here is where the problem lies. As far as we are concerned, the declaration of stuff is an IList<object>. This means that we can add any object to it, including an int or bool. But let’s go back to what we just established. The .NET framework is storing a reference to the underlying declaration, which is actually a List<string>. We cannot add a int or bool to this list because they are not strings. This cleared up something for me I had been wondering for a long time. Let’s elaborate on this:IEnumerable<object> stuff = new List<string>();Currently, just as stated before, .NET will not allow us to do this. But wait you say? It should? I would have to agree. Here’s the reason. IEnumerable is a special class. The reason it is so special is that IEnumerable doesn’t accept modifications. The only thing IEnumerable is responsible for is returning objects, or rather objects coming out. This throws a wrench into what we stated before. Here is a perfectly valid scenario where you should be able to assign this because we will not be making any internal modifications, and could therefore avoid any such typing problems stated in the previous case.
Here, IEnumerable<string> can definitely fill the role of an IEnumerable<object>, because string is said to be the more specific type and object the more generic. To put it another way, string is covariant to object. The only problem is, .NET 3.5 and earlier had no way to deal with this situation. Enter .NET 4.0. Here are a couple interface definitions:
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T> : IEnumerator
{
bool MoveNext();
T Current { get; }
}The new keyword you see, “out”, is specifying that we are only ever going to allow T to come out of our interface. This will allow our previous attempt of assigning IEnumerable<string> to IEnumberable<object> because we just told the compiler that T will never be coming in to our interface.Of course, there is the opposite scenario as well where a type is only going into your interface. This is best demonstrated by looking at the following class:
class Program
{
void Manipulate(object obj)
{
}
void ContravariantGoodness()
{
Action<object> manipulateObject = Manipulate;
Action<string> manipulateString = Manipulate;
manipulateString = manipulateObject;
}
}The only thing you don’t see here is that the Action definition in C# 4.0 contains the in keyword for the generic parameter. As stated previously, this will not work in .NET 3.5 or earlier. In this example, object is contravariant to string because it is going from a more generic type to a more specific type. Because in this case the variable coming in is an object, we can really pass anything (almost) in .NET that will fulfill that contract, including a string. Pretty cool stuff.One major thing to remember is that this is all based around reference types. Something like can never be involved in either of the scenarios:
public struct Parent<T>
{
public T GetT()
{
...
}
}I am really excited to see this in .NET 4.0, and I personally think it is the best of the new features added. Now go check it out.
2 comments:
This was really interesting to me because lately I've been pondering when to make a class generic instead of having an interface-typed object composed/injected in. Clearly one of the factors in the decision to "go generic" is if the class ever needs to return the interface-typed object back OUT again; if so, then the client code might need to handle the return as a concrete type, not through the interface.
Thanks Art! Glad I could help. I really enjoy the power of generics. I personally use generics very frequently because of their ability to use typed objects.
Post a Comment