C# generic method resolution fails with an ambiguous call error
Asked Answered
M

2

13

Suppose I have defined two unrelated types and two extension methods with the same signature but different type filters:

public class Foo {}
public class Bar {}

public static class FooExtensions
{
    public static TFoo Frob<TFoo>(this TFoo foo) where TFoo : Foo { }
    public static TFoo Brob<TFoo>(this TFoo foo) where TFoo : Foo { }
}

public static class BarExtensions
{
    public static TBar Frob<TBar>(this TBar bar) where TBar : Bar { }
}

Then when I write new Foo().Frob(); I get an error

error CS0121: The call is ambiguous between the following methods or properties: 'FooExtensions.Frob<TFoo>(TFoo)' and 'BarExtensions.Frob<TBar>(TBar)'

Could someone explain why this fails and how to avoid it?

EDIT: This happens in VS2015 Update 3 and VS2017 RC.

EDIT2: The idea here is to have fluent API that works on a class hierarchy:

new Foo()
  .Frob()
  .Brob()
Monoceros answered 6/2, 2017 at 22:43 Comment(2)
The accepted answer is correct; I note that what you're doing here is a bad practice. Please don't make extension methods that extend everything, either by extending object, or by extending generic type T. There's almost always a better design. In this case, why not simply extend Foo and Bar? What compelling benefit is there to make these generic?Whiskey
@EricLippert, I have simplified the scenario quite a lot. In reality, the extension methods return TFoo. The idea here is to have fluent API methods that work on a class hierarchy. So I could write new Foo().Frob().Grob().Blob();. The issue arises when I have two independent types, but with extension methods taking similar arguments in same order.Monoceros
G
12

The constraint of a generic type parameter is not part of the method's signature. These two methods are essentially the same from a resolution point of view; when the compiler tries to resolve the call it sees two valid methods and it has no way to choose the better one, therefore the call is flagged as ambiguous.

You can read more about this issue here.

Glottal answered 6/2, 2017 at 22:52 Comment(2)
Thanks for the link; couldn't have said it better myself.Whiskey
@EricLippert Many thanks to you, it's hard to overstate how much I've learned about the language, and programming in general, reading your blog.Glottal
E
0

Actually, the general goal (from your description and my practice and view) is more concise representation for sets of “similar” extension for classes. The “similarity” partially may be that names of these classes are quite long multipart camel-case identifiers which even do not always differ in the first part.

So, using generics is good only in case of class inheritance. For non-inheritable classes solution is using short class aliases.

    public class Foo0987654321 { } 
    public class SucFoo0987654321 : Foo0987654321 { } 
    public class Bar1234567890 { }

    namespace NET_Site
    {
        using B = Bar1234567890;

        public static class BarExtensions
        {
            public static B Frob(this B bar)
            {
                // ...
                return bar;
            }
        }
    }

    namespace NET_Site
    {
        using TFoo = Foo0987654321;

        public static class FooExtensions
        {
            public static TF Frob<TF>(this TF foo)
                where TF : TFoo
            {
                // ...
                return foo;
            }

            public static TF Brob<TF>(this TF foo)
                where TF : TFoo
            {
                // ...
                return foo;
            }
        }
    }

        new SucFoo0987654321()
          .Frob()
          .Brob();
Egyptology answered 10/1, 2021 at 22:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.