class Class1
{
public virtual void Update(T entity)
{
Update(new List() { entity }); //It\'s failed
}
public virtual void
Constraints are not part of the signature, Eric Lippert has a great article about this topic.
OK, let's go through this carefully. We have
Update(new List<T>());
And three candidates -- note that we care only about the signatures of those candidates, so we'll strip away the return types and constraints, which are not part of the signature:
Update(IEnumerable<T> entities)
Update<U>(U entity)
Update<V>(IEnumerable<V> entities)
Our first task is to do type inference on those last two candidates. If inference fails then they are not applicable candidates.
Consider the second method
Update<U>(U entity)
We have an argument of type List<T>
and a formal parameter U
. Therefore we infer that U
is List<T>
.
Consider the third method:
Update<V>(IEnumerable<V> entities)
We have an argument of type List<T>
and a formal parameter of type IEnumerable<V>
. List<T>
implements IEnumerable<T>
so we deduce that V is T.
OK, so our candidate list now consists of:
Update(IEnumerable<T> entities)
Update<List<T>>(List<T> entity)
Update<T>(IEnumerable<T> entities)
Are all of these candidates applicable? Yes. In each case List<T>
is convertible to the formal parameter type. We cannot eliminate any of them yet.
Now that we have only applicable candidates we must determine which one is the unique best.
We can immediately eliminate the third one. The third one and the first one are identical in their formal parameter lists. The rule of C# is that when you have two methods that are identical in their formal parameter lists, and one of them got there "naturally" and one of them got there via type substitution, the substituted one loses.
We can also eliminate the first one. Clearly the exact match in the second one is better than the inexact match in the first one.
That leaves the second one as the last man standing. It wins the overload resolution fight. Then during final validation we discover that the constraint is violated: List<T>
is not guaranteed to be a derived class of T
.
Therefore overload resolution fails. Your arguments caused the best method chosen to be invalid.
If I call
Update((new List<T>() { entity }).AsEnumerable())
, it will be ok.
Correct. Go through it again. Three candidates:
Update(IEnumerable<T> entities)
Update<U>(U entity)
Update<V>(IEnumerable<V> entities)
We have an argument of type IEnumerable<T>
, so we infer the second and third to be:
Update(IEnumerable<T> entities)
Update<IEnumerable<T>>(IEnumerable<T> entity)
Update<T>(IEnumerable<T> entities)
Now we have three applicable candidates with identical parameter lists. The ones that got there under construction are automatically worse than the natural ones, so we eliminate the second and third, leaving only the first. It wins, and it has no constraints to be violated.
It will be ok too when you delete the third method
Your statement is false; this will produce the same error as the first scenario. Taking away the third candidate does not cause the first candidate to suddenly start beating the second candidate.
You are essentially asking why the compiler is not creating an implicit cast from List<T>
to IEnumerable<T>
. The reason is that the C# team made a deliberate design decision that cases of potential ambiguity must be resolved by the programmer, not the compiler. (Note that the VB.NET team made a different decision, to always attempt something sensible that is consistent with the perceived programmer intent.)
The advantages in a case such as this are that surprise is minimized - nothing unexpected can happen under the covers; the disadvantage is the occasional need for more verbose code.