views:

160

answers:

4

Look at the following code:

class A
{
    public string DoSomething(string str)
    {
        return "A.DoSomething: " + str;
    }
}

class B : A
{
}

static class BExtensions
{
    public static string DoSomething(this B b, string str)
    {
        return "BExtensions.DoSomething: " + str;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var a = new A();
        var b = new B();
        Console.WriteLine(a.DoSomething("test"));
        Console.WriteLine(b.DoSomething("test"));

        Console.ReadKey();
    }
}

The output of the code is:

A.DoSomething: test

A.DoSomething: test

When it compiles it gives no warnings.

My questions are: why there are no warnings when that code compiles and what exactly happens when the DoSomething method is called?

+5  A: 

What happens when the method is called is simple: just instance method call. Since C# is early-bound, all methods are resolved at compile-time. In addition, instance methods are preferred over extension ones, so this is why your extension method never gets invoked.

See this:

You can use extension methods to extend a class or interface, but not to override them. An extension method with the same name and signature as an interface or class method will never be called. At compile time, extension methods always have lower priority than instance methods defined in the type itself.

In other words, if a type has a method named Process(int i), and you have an extension method with the same signature, the compiler will always bind to the instance method. When the compiler encounters a method invocation, it first looks for a match in the type's instance methods. If no match is found, it will search for any extension methods that are defined for the type, and bind to the first extension method that it finds.

Anton Gogolev
+1  A: 

This is related to this question, and also this

Ricky AH
+4  A: 

Basically the compiler will always use an instance method if it's available, only resorting to extension methods when everything else fails. From section 7.5.5.2 of the C# 3.0 spec:

In a method invocation (§7.5.5.1) of one of the forms

  • expr . identifier ( )
  • expr . identifier ( args )
  • expr . identifier < typeargs > ( )
  • expr . identifier < typeargs > ( args )

if the normal processing of the invocation finds no applicable methods, an attempt is made to process the construct as an extension method invocation.

This is one of my niggles with the way extension methods are found... the extension method DoSomething will never be called as an extension method (although it's callable with normal static method syntax)... and yet the compiler doesn't even give a warning :(

Jon Skeet
It makes me sad that there are no warning too ):
bniwredyc
A warning would be bad behaviour. Consider the scenario: AlphaCorp implements type Alpha. BetaCorp implements library BetaExt, one method of which adds extension method Frob to Alpha. AlphaCorp says "that's a good idea!" and adds method Frob to Alpha v2. Beta customer Gamma upgrades to latest release of Alpha and now has *a warning they did not cause and cannot suppress without either writing a pragma or abandoning all usage of BetaExt*. That is bad bad bad.
Eric Lippert
+1  A: 

I may be not completely correct, but compiler does something like this: When it gets to

b.DoSomething("test")

it tries to find method with same signature in this or base class, and when it finds the method in base class maps the call to it, simply ignoring extension method.

But if you for example remove base class A from B declaration on the same line compiler will check that no method with such signature exists in this or base class and will replace it with call to static method BExtensions.DoSomething.

You can check it out with .NET Reflector.

When B derives from A:

.locals init (
    [0] class Test.A a,
    [1] class Test.B b)
...
ldloc.1 // loading local variable b
ldstr "test"
callvirt instance string Test.A::DoSomething(string)
call void [mscorlib]System.Console::WriteLine(string)

When B derives from System.Object:

.locals init (
    [0] class Test.A a,
    [1] class Test.B b)
...
ldloc.1 // loading local variable b
ldstr "test"
call string Test.BExtensions::DoSomething(class Test.B, string)
call void [mscorlib]System.Console::WriteLine(string)
Oleg I.