Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Method overloading and generic interface [duplicate]

I'm confused by a problem we have in our project. I tried to simplify it to reproduce the effect:

interface IBar { }

class Bar : IBar {}

interface IFoo<T> where T : IBar { }

class Foo<T> : IFoo<T> where T : IBar { }


class Class1
{
    public void DoTheFoo<T>(T bar) where T : IBar
    {}

    public void DoTheFoo<T>(IFoo<T> foo) where T : IBar
    {}


    public void Test()
    {
        var bar = new Bar();
        var foo = new Foo<Bar>();

        DoTheFoo(bar); // works

        DoTheFoo<Bar>(foo); // works
        DoTheFoo((IFoo<Bar>)foo); // works
        DoTheFoo(foo); // complains
    }
}

To me this looks fine, but the compiler complains on the last call, because it tries to DoTheFoo<T>(T bar), instead of DoTheFoo<T>(IFoo<T> foo) and complains that the argument type does not fit.

  • When I remove the method DoTheFoo<T>(T bar), the last call works!
  • When I change it to DoTheFoo<T>(Foo<T> foo), it works, but I can't use that

It is not too hard to work around this in our current code. But it is a) strange and b) too bad that we can't have these two overloaded methods.

Is there a common rule that explains this behaviour? Is it possible to make it work (except of giving the methods different names)?

like image 286
Stefan Steinegger Avatar asked Mar 26 '13 13:03

Stefan Steinegger


1 Answers

It's just a matter of type inference not quite working in your favour when combined with overload resolution. It's easy to fix by just specifying the type argument explicitly - no cast is required:

DoTheFoo<Bar>(foo);

Usually I'm nervous of overloads which take fairly different parameter types though. Often the code ends up simpler if you just give the methods different names. Aside from anything else, then your readers don't need to try to perform overload resolution at the same time as type inference...

EDIT: I believe the problem is that the ordering works like this:

  • Both methods are found
  • Type inference is applied to both methods without validating the constraints - so for the first method, we get T = Foo<Bar> and for the second method we get T = Bar. Both methods are applicable at this point.
  • Overload resolution is performed, which decides that the first method is the most specific one.
  • Only after overload resolution has been performed is the constraint on T checked - and that fails because there's no reference conversion from Bar to IFoo.

There's an Eric Lippert blog post about why the language is designed this way, a blog post I wrote about it, and an article I wrote about overloading in general. Each of them may or may not help :)

EDIT: Leaving type inference aside for a moment, the reason the first method is more specific is that in one case we're converting from Foo<Bar> to Foo<Bar>, and in the other we're converting from Foo<Bar> to IFoo<Bar>. As per section 7.5.3.3 of the C# 5 specification:

Given an implicit conversion C1 that converts from an expression E to a type T1, and an implicit conversion C2 that converts from an expression E to a type T2, C1 is a better conversion than C2 if at least one of the following holds: - E has a type S and an identity conversion exists from S to T1 but not from S to T2 - ...

The identity conversion is from a type to itself, which is the case for the first overload, but isn't for the second. So the first conversion is better.

like image 95
Jon Skeet Avatar answered Oct 15 '22 15:10

Jon Skeet