Evil Code of the Day: variance and overloading

(Note that this kind of breakage was mentioned a long time ago in Eric Lippert’s blog, although not in this exact form.)

Whenever a conversion becomes available where it wasn’t before, overload resolution can change its behaviour. From C# 1 to C# 2 this happened due to delegate variance with method group conversions – now the same thing is true for generic variance for interfaces.

What does the following code print?

using System;
using System.Collections.Generic;

class Base
{
    public void Foo(IEnumerable<string> strings)
    {
        Console.WriteLine(“Strings”);
    }
}

class Derived : Base
{
    public void Foo(IEnumerable<object> objects)
    {
        Console.WriteLine(“Objects”);
    }
}

class Test
{
    static void Main()
    {
        List<string> strings = new List<string>();
        new Derived().Foo(strings);
    }
}

The correct answer is “it depends on which version of C# and .NET framework you’re using.”

If you’re using C# 4.0 and .NET 4.0, then IEnumerable<T> is covariant: there’s an implicit conversion from IEnumerable<string> to IEnumerable<object>, so the derived overload is used.

If you’re using C# 4.0 but .NET 3.5 or earlier then the compiler still knows about variance in general, but the interface in the framework doesn’t have the appropriate metadata to indicate it, so there’s no conversion available, and the base class overload is used.

If you’re using C# 3.0 or earlier then the compiler doesn’t know about generic variance at all, so again the base class overload is used.

So, this is a breaking change, and a fairly subtle one at that – and unlike the method group conversion in .NET 2.0, the compiler in .NET 4.0 beta 1 doesn’t issue a warning about it. I’ll edit this post when there’s an appropriate Connect ticket about it…

In general though, I’d say it’s worth avoiding overloading a method declared in a base class unless you really have to. In particular, overloading it using the same number of parameters but more general ones seems to be a recipe for unreadable code.

6 thoughts on “Evil Code of the Day: variance and overloading”

  1. “In general though, I’d say it’s worth avoiding overloading a method declared in a base class unless you really have to. In particular, overloading it using the same number of parameters but more general ones seems to be a recipe for unreadable code.”
    Agreed.  I’m always interested in Eric Lippert’s blog articles in which he discusses these kinds of corner cases that make changes to the language difficult or impossible.  But one common thread among practically all of the examples is that typically something that may confuse the compiler or otherwise break code that was already working turns out to be something that probably shouldn’t have been coded that way anyway.

    Like

  2. I generally forget this is even possible in C#, just from C++ habit. In C++ to make overloads from Base visible in Derived, you have to add something like:

    using Base::Foo;

    to Derived. But I only ever used that trick when meddling in the darkest of template-fu. Otherwise, best avoided (but then, implementation inheritance generally tends to be a deep well of problems along these lines).

    Like

  3. “If you’re using C# 4.0 and .NET 4.0, then IEnumerable is covariant: there’s an implicit conversion from IEnumerable to IEnumerable, so the derived overload is used.”

    Won’t compiler choose a better match in this case, i.e. the base class overload?

    Like

Leave a comment