Aug 25 2008

Translate List<A> to List<B> With LINQ

Category: .Net Framework | TipsAlexRobson @ 17:23

Let me just skip to the fun bit that the title promised before going off on some rant-flavored-jelly-filled-technical-jargon-journey that you really didn't pay for. If you have a generic List of type A and you want a generic List of type B, AND you know A can be cast as B then you could do the following:

List<B> TranslateList<A, B>(List<A> listofATypeThingies)
{
    List<B> listofBTypeThingies;
    
    foreach(A item in listofATypeThingies)
    {
        listofBTypeThingies.Add((B) item);
    }
 
    return listofBTypeThingies;
}

And then call TranslateList all over the place until the end of time. I prefer the following LINQified snippet:

List<B> listofBTypeThingies = new List<B>(listofATypeThingies.Select(item => (B) item));

Yes. The appropriate response in this case would be "Oh snap!"

*** EDIT ***

Fortunately for all of us, someone smarter than me is reading this blog so that they can correct me when I post something ignorant (like just now). There's already an extension method that addresses this particular use case and can be used like so:

   1: List<B> listofBTypeThingies = new List<B>(listofATypeThingies.Cast<B>());

Thanks to Jeff Cutsinger for catching this one. (If you ever start posting to a blog, Jeff, I'll link it :)

******

If you want to know WHY you have to do this in the first place read on. Otherwise, ignorance really is bliss. I know, because I'm still pretty ignorant and consequently, moderately happy.

First you need to speak the language of computer science. No, I'm not going to regurgitate text book definitions at you, if you want that, read a text book. But in order to understand the why, you need to know the following definitions (someone correct me if I'm butchering this) covariance allows the implicit conversion from a base type to a derived type, contravariance allows implicit conversion from a derived type to a base type and invariance disallows type conversion.

.Net allows for covariance in return types, function arguments, delegates and arrays. Generic type parameters are always invariant. At first I was irritable because I thought it was an unreasonable limitation, but then I started to play around with all this and now I understand why there's a genuine need for some type invariance in statically typed languages that allow side effects. Behold, I give you example code:

   1: public class MotherOfAllClasses
   2: {
   3: }
   4:  
   5: public class Child1 : MotherOfAllClasses
   6: {    
   7: }
   8:  
   9: public class Child2 : MotherOfAllClasses
  10: {    
  11: }
  12:  
  13: public class TypeShinanigans
  14: {
  15:     public void ThorDestroyerOfTypeSafety()
  16:     {
  17:         Child1 child1 = new Child1();
  18:         Child2 child2 = new Child2();
  19:  
  20:         MotherOfAllClasses mom1 = new MotherOfAllClasses();
  21:         MotherOfAllClasses mom_Child1 = new Child1();
  22:         MotherOfAllClasses mom_Child2 = new Child2();
  23:  
  24:         //This line is legal because mom_Child1 actually
  25:         //holds a reference to an instance of Child1
  26:         Child1 workingCast1 = (Child1)mom_Child1;
  27:  
  28:         //Here is some wonderfully breaking code
  29:         //which upon first glance may seem legal
  30:         //and even more disturbingly will build
  31:         
  32:         //Child1 breakingCast1 = (Child1) mom1;
  33:         //Child2 breakingCast2 = (Child2) mom1;
  34:         //Child1 breakingCast3 = (Child1) mom_Child2;
  35:  
  36:         //The third line here will break because we're
  37:         //trying to sneak in an instance of the parent class
  38:         //into an array of Child1 which as we saw earlier is
  39:         //a runtime exception
  40:         Child1[] chillenz1 = new Child1[5];
  41:         MotherOfAllClasses[] mamaz = chillenz1;
  42:         mamaz[0] = new MotherOfAllClasses();
  43:     }
  44: }

Hopefully that's helpful to understand WHY variance is dangerous enough to begin with but would be especially easy to break in conjunction with generic usage. If you'd like to discuss it more, I'll tell you what I can but I'm at a slight disadvantage not having taken lambda calculus (no, I'm not making it up, it's a real class).

Tags: ,

blog comments powered by Disqus