This weekend, I finally decided to step into the 21st century and began the process of migrating RSS Bandit to v2.0 of the .NET Framework. In addition, we've also moved our source code repository from CVS to Subversion and so far it's already been a marked improvement. Since the .NET Framework is currently on v3.0 and v3.5 is in beta 1, I'm fairly out of date when it comes to the pet peeves in my favorite programming tools. At least one of my pet peeves was fixed, in Visual Studio 2005 I finally have an IDE where "Find References to this Method" actually works. On the flip side, the introduction of generics has added a lot more frustrating moments than I expected. By now most .NET developers have seen the dreaded

Cannot convert from 'System.Collections.Generic.List<subtype of T> to 'System.Collections.Generic.List<T>'

For those of you who aren't familiar with C# 2.0, here are examples of code that works and code that doesn't work. The difference is often subtle enough to be quite irritating when you first encounter it.

WORKS! - Array[subtype of T] implicitly casted to Array[T]

using System;

using Cybertron.Transformers;



public class TransformersTest{



public static void GetReadyForBattle(Transformer[] robots){

foreach(Transformer bot in robots){

if(!bot.InRobotMode)

bot.Transform();

}

}



public static void Main(string[] args){



Autobot OptimusPrime = new Autobot();

Autobot[] autobots = new Autobot[1];

autobots[0] = OptimusPrime;



Decepticon Megatron = new Decepticon();

Decepticon[] decepticons = new Decepticon[1];

decepticons[0] = Megatron;



GetReadyForBattle(decepticons);

GetReadyForBattle(autobots);



}

}





DOESN'T WORK - List<subtype of T> implicitly casted to List<T>



using System;

using Cybertron.Transformers;



public class TransformersTest{



public static void GetReadyForBattle(List<Transformer> robots){

foreach(Transformer bot in robots){

if(!bot.InRobotMode)

bot.Transform();

}

}



public static void Main(string[] args){



Autobot OptimusPrime = new Autobot();

List<Autobot> autobots = new List<Autobot>(1);

autobots.Add(OptimusPrime);



Decepticon Megatron = new Decepticon();

List<Decepticon> decepticons = new List<Decepticon>(1);

decepticons.Add(Megatron);



GetReadyForBattle(decepticons);

GetReadyForBattle(autobots);



}

}

The reason this doesn't work has been explained ad nauseum by various members of the CLR and C# teams such as Rick Byer's post Generic type parameter variance in the CLR where he argues

More formally, in C# v2.0 if T is a subtype of U, then T[] is a subtype of U[], but G<T> is not a subtype of G<U> (where G is any generic type). In type-theory terminology, we describe this behavior by saying that C# array types are “covariant” and generic types are “invariant”. There is actually a reason why you might consider generic type invariance to be a good thing. Consider the following code: List<string> ls = new List<string>(); ls.Add("test"); List<object> lo = ls; // Can't do this in C# object o1 = lo[0]; // ok – converting string to object lo[0] = new object(); // ERROR – can’t convert object to string If this were allowed, the last line would have to result in a run-time type-check (to preserve type safety), which could throw an exception (eg. InvalidCastException). This wouldn’t be the end of the world, but it would be unfortunate.



Even if I buy that the there is no good way to prevent the error scenario in the above code snippet without making generic types invariant, it seems that there were a couple of ways out of the problem that were shut out by the C# language team. One approach that I was so sure would work was to create a subtype of System.Collections.Generics.List and define implict and explict cast operators for it. It didn't

WORKS! - MyList<T> implicitly casted to ArrayList via user-defined cast operator



using System;

using Cybertron.Transformers;





public class MyList<T>: List<T>{



public static implicit operator MyList<T>(ArrayList target){

MyList<T> newList = new MyList<T>();



foreach(T item in target){

newList.Add(item);

}

return newList;

}

}



public class Test{



public static void GetReadyForBattle(MyList<Transformer> robots){

foreach(Transformer bot in robots){

if(!bot.InRobotMode){

bot.Transform();

}

}

}



public static void Main(string[] args){



Autobot OptimusPrime = new Autobot();

ArrayList autobots = new ArrayList(1);

autobots.Add(OptimusPrime);



Decepticon Megatron = new Decepticon();

ArrayList decepticons = new ArrayList(1);

decepticons.Add(Megatron);



GetReadyForBattle(decepticons);

GetReadyForBattle(autobots);

}

}

DOESN'T WORK - MyList<subtype of T> implicitly casted to MyList<T> via user-defined cast



using System;

using Cybertron.Transformers;





public class MyList<T>: List<T>{



public static implicit operator MyList<T>(MyList<U> target) where U:T{

MyList<T> newList = new MyList<T>();



foreach(T item in target){

newList.Add(item);

}

return newList;

}



}



public class Test{



public static void GetReadyForBattle(MyList<Transformer> robots){

foreach(Transformer bot in robots){

if(!bot.InRobotMode)

bot.Transform();

}

}



public static void Main(string[] args){



Autobot OptimusPrime = new Autobot();

MyList<Autobot> autobots = new MyList<Autobot>(1);

autobots.Add(OptimusPrime);



Decepticon Megatron = new Decepticon();

MyList<Decepticon> decepticons = new MyList<Decepticon>(1);

decepticons[0] = Megatron;



GetReadyForBattle(decepticons);

GetReadyForBattle(autobots);

}

}



I really wanted that last bit of code to work because it would have been quite a non-intrusive fix for the problem (ignoring the fact that I have to use my own subclasses of the .NET Framework's collection classes). At the end of the day I ended up creating a TypeConverter utility class which contains some of the dumbest code I've had to write to trick a compiler into doing the right thing, here's what it ended up looking like



WORKS - Create a TypeConverter class that encapsulates calls to List.ConvertAll

using System;

using Cybertron.Transformers;





public class TypeConverter{



public static List<Transformer> ToTransformerList<T>(List<T> target) where T: Transformer{

return target.ConvertAll(new Converter<T,Transformer>(MakeTransformer));

}



public static Transformer MakeTransformer<T>(T target) where T:Transformer{

return target; /* greatest conversion code ever!!!! */

}



}



public class Test{



public static void GetReadyForBattle(List<Transformer> robots){

foreach(Transformer bot in robots){

if(!bot.InRobotMode)

bot.Transform();

}

}

}



public static void Main(string[] args){



Autobot OptimusPrime = new Autobot();

List<Autobot> autobots = new List<Autobot>(1);

autobots.Add(OptimusPrime);



Decepticon Megatron = new Decepticon();

List<Decepticon> decepticons = new List<Decepticon>(1);

decepticons.Add(Megatron);



GetReadyForBattle(TypeConverter.ToTransformerList(decepticons));

GetReadyForBattle(TypeConverter.ToTransformerList(autobots));

}

}

This works but it's ugly as sin. Anybody got any better ideas?

TypeConverter

TypeConverter

Lot's of great suggestions in the comments. Since I don't want to go ahead and modify a huge chunk of methods across our code base, I suspect I'll continue with themodel. However John Spurlock pointed out that it is much smarter to implement theusing generics for both input and output parameters instead of way I hacked it together last night. So our code will look more like

using System;

using Cybertron.Transformers;





public class TypeConverter{



/// <summary>

/// Returns a delegate that can be used to cast a subtype back to its base type.

/// </summary>

/// <typeparam name="T">The derived type</typeparam>

/// <typeparam name="U">The base type</typeparam>

/// <returns>Delegate that can be used to cast a subtype back to its base type. </returns>

public static Converter<T, U> UpCast<T, U>() where T : U {

return delegate (T item) { return (U)item; };

}

}





public class Test{



public static void GetReadyForBattle(List<Transformer> robots){

foreach(Transformer bot in robots){

if(!bot.InRobotMode)

bot.Transform();

}

}

}



public static void Main(string[] args){



Autobot OptimusPrime = new Autobot();

List<Autobot> autobots = new List<Autobot>(1);

autobots.Add(OptimusPrime);



Decepticon Megatron = new Decepticon();

List<Decepticon> decepticons = new List<Decepticon>(1);

decepticons.Add(Megatron);



GetReadyForBattle(decepticons.ConvertAll(TypeConverter.UpCast<Decepticon, Transformer>()));

GetReadyForBattle(autobots.ConvertAll(TypeConverter.UpCast<Autobot, Transformer>()));

}

}