Generic variance and List(Of T)

Posted Fri, Aug 8 2008 15:55 by bill

This post has been sitting in my drafts for a while, so I thought I should post it, mainly because I want to talk about this and generic variance and arrays in more detail in the days ahead.  The reason this post was put on hiatus was I was waiting for my article on arrays to appear in Visual Studio Magazine, as that deals with many of the details as to why this works ;)

 

Have you ever wanted to cast a List(Of Customer) to a List(Of BusinessBase), where Customer Inherits BusinessBase, only to find that you can't... well you can ;)

 

This extension will return an IList of BusinessBase for an input of a List(Of Customer).  Be aware it is the underlying array, so you will need to get the actual count from the original input.

 
   <Runtime.CompilerServices.Extension()> _
   Function ToIList(Of T, TBase)(ByVal list As List(Of T)) As IList(Of TBase)
      Dim fi = GetType(List(Of T)).GetField("_items", Reflection.BindingFlags.Instance Or _
                                                      Reflection.BindingFlags.GetField Or _
                                                      Reflection.BindingFlags.NonPublic)
      Return CType(fi.GetValue(list), IList(Of TBase))
   End Function

 

 

And a quick test:

 
 
Module Module1
 
   Sub Main()
      Dim myApples As New List(Of Apple)
      myApples.Add(New Apple With {.Name = "Golden Delicious"})
      myApples.Add(New Apple With {.Name = "Red Delicious"})
      myApples.Add(New Apple With {.Name = "Granny Smith"})
 
 
      Dim fruits As IList(Of Fruit) = myApples.ToIList(Of Fruit)()
 
      For i = 0 To myApples.Count - 1
         fruits(i).Name = "Fruit : " & fruits(i).Name
      Next
 
      For Each a In myApples
         Console.WriteLine(a.Name)
      Next
 
      Console.WriteLine("finished")
 
      Console.ReadLine()
 
   End Sub
 
 
 
   <Runtime.CompilerServices.Extension()> _
   Function ToIList(Of T, TBase)(ByVal list As List(Of T)) As IList(Of TBase)
      Dim fi = GetType(List(Of T)).GetField("_items", _
                                         Reflection.BindingFlags.Instance Or _
                                         Reflection.BindingFlags.GetField Or _
                                         Reflection.BindingFlags.NonPublic)
      Return CType(fi.GetValue(list), IList(Of TBase))
   End Function
 
End Module
 
 
 
 
Class Fruit
 
 
   Private _Name As String
 
   Public Property Name() As String
      Get
         Return _Name
      End Get
      Set(ByVal value As String)
         _Name = value
      End Set
   End Property
 
 
End Class
 
Class Apple : Inherits Fruit
 
End Class
Filed under: , , , , ,

Comments

# re: Generic variance and List(Of T)

Friday, August 08, 2008 9:48 AM by David M. Kean

Be aware that you should *never* rely on the internal and private members of types. These are free to change from version to version (even in service packs). Instead, simply use the Enumerable.Cast<T>() method from System.Linq instead.

# re: Generic variance and List(Of T)

Friday, August 08, 2008 10:45 AM by bill

Hi David,

Enumerable.Cast(Of T) is completely different.  It returns only an IEnumerable(Of T), not an IList(Of T). The point of this example is to show generic variance of arrays, not creating new iterators.

And yes it is unfortunate that List(Of T) doesn't let any derived class get at the internal store, hence the need for reflection.

# re: Generic variance and List(Of T)

Sunday, August 10, 2008 5:28 PM by Hoop Somuah

You seem to be piggybacking on the fact that Arrays i nthe CLR are covariant and that they implement IList<T> and that List<T> uses an array for internal storage. That last one could change.

Why wouldn't you write yiour own implementation of Cast<T> for IList that wraps the list (if necessary). If you got fancy enough, you could avoid recursive wrapping which is one concern and IT wouldn't require you to rely on the underlying store being an Array. The only reason Arrays in the .Net CLR support covariance is because Java supports it and running Java on the CLR was desired.

BTW: What would you expect to have happen if someone Casts IList<Apple> to IList<Fruit> and then passes that to a function that expects and IList<Fruit> which then proceeds to try and add an Orange to the list? Seems like a messy thing to support generaly in the framework.

In your case you'll get an ArrayTypeMismatchException.

I wonder how the casting to IList<T> is implemented internally by Arrays in the CLR. I can cast a strin[] to IList<object> without a problem...

# re: Generic variance and List(Of T)

Sunday, August 10, 2008 10:32 PM by bill

Hi Hoop,

Yes the above is just messing with the generic varaince of arrays.  It's fragile, and I really don't recommend it.  

As to your question on what happens when you have an IList(Of Apple) cast to IList(Of Fruit), that issue exists today with arrays anyway.  Anywhere you have a parameter defiend as IList(Of T) you should be checking for the possibility of array variance if you want to avoid throwing the mismatch exception.

The same applies to ICollection(Of T), although there you are more likely ot hit a not implemented exception rather than have any variance issues.

# Generisches Problem | hilpers

Saturday, January 17, 2009 11:40 AM by Generisches Problem | hilpers

Pingback from  Generisches Problem | hilpers