Overloading & Co/Contra-variance could break your code!

Co and contra-variance were introduced to VB.NET and C# to make working with certain classes more natural (“because it should work”). But beware, I was experimenting a bit with this and found following possible breaking change. I started with these two classes:

C#

   1: public class Person
   2: {
   3:   // ...
   4: }
   5:  
   6: public class Vip : Person
   7: {
   8:   // ...
   9: }

VB.NET

   1: Public Class Person
   2:   ' 
   3: End Class
   4:  
   5: Public Class Vip
   6:   Inherits Person
   7:   '
   8: End Class

Then I added a collection of people:

C#

   1: public class PersonList
   2: {
   3:   public void Add(object obj) {
   4:     // ...
   5:   }
   6:  
   7:   public void Add(Person p)
   8:   {
   9:     // ...
  10:   }
  11:  
  12:   public void Add(IEnumerable<Person> people)
  13:   {
  14:     foreach (Person p in people)
  15:     {
  16:       // ...
  17:     }
  18:   }
  19: }

VB.NET

   1: Public Class PersonList
   2:   Public Sub Add(ByVal obj As Object)
   3:     '
   4:   End Sub
   5:  
   6:   Public Sub Add(ByVal person As Person)
   7:     ' 
   8:   End Sub
   9:  
  10:   Public Sub Add(ByVal list As IEnumerable(Of Person))
  11:     ' 
  12:   End Sub
  13: End Class

Next I create a PersonList collection and then add a list of Vip’s:

C#

   1: class Program
   2: {
   3:   static void Main(string[] args)
   4:   {
   5:     PersonList people = new PersonList();
   6:  
   7:     List<Vip> others = new List<Vip> {
   8:       new Vip(), new Vip()
   9:     };
  10:  
  11:     people.Add(others);
  12:   }
  13: }

VB.NET

   1: Sub Main()
   2:   Dim people As New PersonList
   3:   Dim others As New List(Of Vip)(New Vip() {New Vip(), New Vip()})
   4:   people.Add(others)
   5: End Sub

When I compile this in Visual Studio 2008 the first Add method, taking the object argument, gets called.

But with Visual Studio 2010 (using the .NET 4 target) the third Add method gets called. This is because now IEnumerable<T> is co-variant and will now convert the List<Vip> to an IEnumerable<Person>.

Ok, granted. This is NOT something you will see every day, but if you ever encounter it, this might be very confusing.

This kind of problem can also occur with extension methods and user-defined conversions…