views:

134

answers:

3

Hello, I just hit a situation where a method dispatch was ambiguous and wondered if anyone could explain on what basis the compiler (.NET 4.0.30319) chooses what overload to call

interface IfaceA
{

}

interface IfaceB<T>
{
    void Add(IfaceA a);
    T Add(T t);
}

class ConcreteA : IfaceA
{

}

class abstract BaseClassB<T> : IfaceB<T>
{
    public virtual T Add(T t) { ... }
    public virtual void Add(IfaceA a) { ... }
}

class ConcreteB : BaseClassB<IfaceA>
{
    // does not override one of the relevant methods
}

void code()  
{
    var concreteB = new ConcreteB();

    // it will call void Add(IfaceA a)
    concreteB.Add(new ConcreteA());
}

In any case, why does the compiler not warn me or even why does it compile? Thank you very much for any answers.

+1  A: 

Because the compiler chooses the most specific first.

What happens if you call like so:

void code()   
{ 
    var concreteB = new ConcreteB(); 

    IfaceA  x = concreteB.Add(new ConcreteA()); 
} 
Mitch Wheat
But why would `void Add(IfaceA a)` be any more specific? Is it because I do not use the returned result? And besides, do you know if I there is a compiler option to yield me a warning?
Sebastian Godelet
@sebgod: you can use the `#warning` directive to issue a compiler warning.
Tomas Lycken
The code will not compile if i choose `IfaceA x = concreteB.Add(new ConcreteA());` : cannot implicitly convert between IFaceA and void
Sebastian Godelet
@sebgod: interesting. Perhaps one for the Skeet...
Mitch Wheat
it looks like the compiler kind of does only consider the interface based method and does not check the generic overload, btw, what does "one for the Skeet" mean?
Sebastian Godelet
As you see this is my first question on SO, I'm sad I cannot vote up your answer, says I need 15 points, I think this morning the coffee wasn't good ^^
Sebastian Godelet
@Mitch: I was busy typing it up :)
Jon Skeet
@Tomas, with warning I mean that the compiler would tell me about it, not I want to issue the warning (this was all a silent code break)
Sebastian Godelet
+1  A: 

This somewhat reminds me of the "Type inference a-go-go" in Jon Skeet's BrainTeaser. If you do not want to trust the compiler, you might want to force its choice by calling Add<ConcreteA>(new ConcreteA())

Sylvestre Equy
Thats what my teacher always told me, never trust your compiler
Sebastian Godelet
+2  A: 

It follows the rules in section 7.5.3.2 of the C# 4 specification ("Better function member").

First (well, after seeing that both methods are applicable) we need to check the conversions from argument types to parameter types. In this case it's reasonably simple because there's only one argument. Neither conversion of argument type to parameter type is "better" because both are converting from ConcreteA to IfaceA. It therefore moves on to the next set of criteria, including this:

Otherwise, if MP has more specific parameter types than MQ, then MP is better than MQ. Let {R1, R2, …, RN} and {S1, S2, …, SN} represent the uninstantiated and unexpanded parameter types of MP and MQ. MP’s parameter types are more specific than MQ’s if, for each parameter, RX is not less specific than SX, and, for at least one parameter, RX is more specific than SX:specific than SX:

  • A type parameter is less specific than a non-type parameter.
  • ...

So even though the conversion is equally good, the overload using IfaceA directly (rather than via delegates) is deemed "better" because a parameter of type IfaceA is more specific than a parameter of type T.

There's no way of getting the compiler to warn on this behaviour - it's just normal overload resolution.

Jon Skeet
Ok thanks a million. I guess that means I will read that specification. I am just a Java convert and have to use C# for this project. Although in fact I think its more useful, but any feature I feel adds also some edge cases more ...
Sebastian Godelet