C# generic overload - Compiler can't determine

2019-05-01 03:58发布

问题:

I don't understand why the compiler can't resolve the correct overload to use here. (code below) There is only one version of Add() that is appropriate- BigFoo is an IFoo, and does not implement IEnumerable where T is an IFoo. But it insists on reporting an ambiguity. Any ideas? I tried adding a second generic type parameter- Add where T : IFoo where U : IEnumerable. But then the overload is completely ignored even for legitimate use.

I know I can work around this with casting and specifying generic type parameters but at that point I've defeated the purpose of having an overload. You could question the overload, but the semantics feel correct to me- the behavior I'm implementing in my class is for both Add() to add the object wholesale as an individual entry in the collection. (the second Add() is not supposed to be an AddRange().)

namespace NS
{
  interface IFoo { }

  class BigFoo : IFoo, IEnumerable<int>
  {
    public IEnumerator<int> GetEnumerator()
    {
      throw new NotImplementedException();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
      throw new NotImplementedException();
    }
  }

  class FooContainer
  {
    public void Add(IFoo item) { }
    public void Add<T>(IEnumerable<T> group) where T : IFoo { }
  }

  class DemoClass
  {
    void DemoMethod()
    {
      BigFoo bigFoo = new BigFoo();
      FooContainer fooContainer = new FooContainer();
      // error CS0121: The call is ambiguous between the following methods or properties: 
      // 'NS.FooContainer.Add(NS.IFoo)' and 
      // 'NS.FooContainer.Add<int>(System.Collections.Generic.IEnumerable<int>)'
      fooContainer.Add(bigFoo);
    }
  }
}

回答1:

Generic overload resolution doesn't take constraints into account, so it deems the Add<T> version to be applicable, inferring T=int.

Both methods are applicable, and neither is definitely better than the other, as there is no conversion between IEnumerable<int> and IFoo. While generic methods are deemed "less specific" than non-generic methods, this only becomes relevant when the parameter types are identical after type argument replacement, which they're not in this case.



回答2:

In FooContainer, on the second "Add" you are constraining T to be of type IFoo. BigFoo implements the IFoo interface, therefore it kinda matches that Add definition (even though it doesn't really, because it doesn't implement IEnumable<IFoo>).

I'm not sure I understand completely what you want, but I suspect it is this:

public void Add<T>(T group) where T : IEnumerable<IFoo> { }

which would allow you to add any object T where T is an enumerable set of IFoo objects.

Is that what you wanted?

Regards, Richard



回答3:

The problem here is that generic type constraints are completely ignored by the compiler (it only looks at parameter types). As far as the compiler is concerned, the IEnumerable<T> argument being passed could just as well be a IEnumerable<IFoo>.

For complete information on this subject, refer to section 25.6.4 Inference of type arguments of the C# Language Specification. Note that there is no mention of the utilisation of type constraints.



回答4:

The compiler should be smart enough to recognize that BigFoo can't be cast to IEnumerable<IFoo>, but it isn't. It simply sees that it's an IEnumerable<T>, and feels that it's a potential overload candidate (even though the contstraint you defined enforces that T must be IFoo and int can't be cast to IFoo). While it's inconvenient, it's not that big of a deal. Just cast bigFoo to IFoo and the compiler will be happy:

fooContainer.Add((IFoo)bigFoo);

Alternately, you can make your generic overload of Add uglier:

public void Add<T, U>(U group)
    where T : IFoo
    where U : IEnumerable<T>
{
}

Either way, you have more typing, the second solution eliminates the need to cast calls to Add, but you will have to explicitly declare type on calls to the generic add (which ends up being more code:

fooContainer.Add<IFoo, IEnumerable<IFoo>>(enumerableFoo);


回答5:

Interesting.... Just tried your sample out. Generics continues to keep me on my toes.

//1 - First preference
public void Add(BigFoo item) { Console.WriteLine("static BigFoo type Add"); }
//2 - Second Preference
public void Add<T>(T item) { Console.WriteLine("Generic Add");  }
//3 - Third preferences
public void Add(IFoo item) { Console.WriteLine("static IFoo interface Add"); }
//4 - Compiles if 1-4 exist. Compile error (ambiguity) if only 3-4 exist. Compile error (cannot convert int to IFoo) if only 4 exists
public void Add<T>(IEnumerable<T> group) where T : IFoo { }