10

Please check the following codes segments:

public interface ICountable { }
public class Counter<T>
    where T : ICountable
{
    public int Count(IEnumerable<T> items)
    {
        return 0;
    }

    public int Count(T Item)
    {
        return 0;
    }
}

public class Counter
{
    public int Count<T>(IEnumerable<T> items)
        where T : ICountable
    {
        return 0;
    }

    public int Count<T>(T Item)
        where T : ICountable
    {
        return 0;
    }
}

The two versions of Counter differ only in the specification of the generic parameter. One of them defines as a generic type parameter, the other as a generic argument. Both restrict the method arguments to implement the ICountable interface. I will call them specific and non specific respectively.

Now, I am defining a class that implements the ICountable interface, and a collection of instances:

public class CItem : ICountable { }
var countables = new List<CItem>();

Then, I would like to use both Counter classes on the collection.

var specific = new Counter<CItem>();
var nonspecific = new Counter();

specific.Count(countables);
nonspecific.Count(countables);

The specific counter recognizes that the countables collection should fall into the signature int Count(IEnumerable), but the non specific version does not. I get the error:

The type 'System.Collections.Generic.List<CItem>' cannot be used as type parameter 'T' in the generic type or method 'Counter.Count<T>(T)'. There is no implicit reference conversion from List<CItem>' to ICountable.

It seems that the non specific version uses the wrong signature for the collection.

Why do they behave differently? How can the non specific version be specified in order to behave the same as the other?

Note: I know this example is not realistic. However, I faced this problem in a quite complicate scenario with extension methods. I use these classes for the sake of simplicity

Thanks in advance

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
Daniel Leiszen
  • 1,827
  • 20
  • 39
  • 2
    if you specify generic type explicitly on like `nonspecific.Count(countables);` that will work too – farid bekran Dec 16 '15 at 10:59
  • 1
    if you define `countables` with `IEnumerable countables = new List();` the correct overload is chosen. – SWeko Dec 16 '15 at 11:03
  • 1
    The two Count() method overloads are ambiguous, both could take a List as an argument. However, one of them violates the constraint in the *specific* case of you passing a List. That's not good enough, if you'd use another type that implements both IEnumerable *and* ICountable then it is truly ambiguous. C# does not allow you to declare a generic type/method that can randomly fail to compile. – Hans Passant Dec 16 '15 at 11:07

3 Answers3

4

The problem with nonspecific class is that compiler doesn't know the type T in compile time that's why it cannot select correct overload for method Count<T>(). However if you set generic type constraints compiler now knows what type to expect...

If you'll comment out your method with signature public int Count<T>(T Item) it'll compile because it'll use method with correct signature (which is public int Count<T>(IEnumerable<T> items))

It'll also compile and run if you help compiler to infer type by casting your List to IEnumerable<CItem> explicitly :

nonspecific.Count(countables as IEnumerable<CItem>);

Have a look at simplified scenario :

    static string A<T>(IEnumerable<T> collection)
    {
        return "method for ienumerable";
    }

    static string A<T>(T item)
    {
        return "method for single element";
    }

    static void Main(string[] args)
    {
        List<int> numbers = new List<int>() { 5, 3, 7 };
        Console.WriteLine(A(numbers));
    }

Output : "method for single element"

Fabjan
  • 13,506
  • 4
  • 25
  • 52
  • Yes, I experienced the same. In my scenario, I have different extension methods for collections and single instances of the same type. I would expect the compiler to select the signature with a collection parameter, when I pass a collection and the signature with an instance parameter, when I pass a signle instance. Is that an unrealistic expectation? – Daniel Leiszen Dec 16 '15 at 10:59
  • @DanielLeiszen Yes because that's how Generic parameters work in C# Compiler doesn't know type on compile time, only at run-time... However if you set type constraints you 'tell compiler' what type it should expect. – Fabjan Dec 16 '15 at 11:00
2

If I remember correctly (will try to find a reference in the specification), the T method is chosen because it's an exact match for the type.

The type inference, correctly identifies that both generic methods are applicable, as Count<CItem>(IEnumerable<CItem> items) and Count<List<CItem>>(List<CItem> items). However, the first one loses in the overload resolution, as the second one is more specific. The constraints only come in play after that, so you get a compile time error.

If you declare your countables using

IEnumerable<CItem> countables = new List<CItem>();

then the choice becomes Count<CItem>(IEnumerable<CItem> items) and Count<IEnumerable<CItem>>(IEnumerable<CItem> items) and the first one wins the overload resolution.

haim770
  • 48,394
  • 7
  • 105
  • 133
SWeko
  • 30,434
  • 10
  • 71
  • 106
  • 1
    Thanks for the post. I marked Fabjan's solution as answer, since that was the first. But your post also answers the question. – Daniel Leiszen Dec 16 '15 at 11:12
  • @DanielLeiszen, no problem, it was an interesting question - now I have to dig through the spec to find why it works specifically in the second case. – SWeko Dec 16 '15 at 11:15
1

In my opinion, the reason why the compiler thinks that you are calling Counter.Count(T) instead of Counter.Count< T >(IEnumerable< T >) is because the later one requires a conversion from List to IEnumerable. And that has a priority less than using the former signature Counter.Count(T), which result in an error.

I think it's better that you change the method name of the one taking an IEnumerble as the argument into something like CountAll. The some thing .NET framework does for List.Remove and List.RemoveAll. It's a good practice to make your code more specific rather than letting the compiler to do all the decisions.

fjch1997
  • 1,518
  • 18
  • 19