views:

64

answers:

3

Invoking an extension method that works on a interface from an implementor seems to require the use of the this keyword. This seems odd.

Does anyone know why?

Is there an easier way to get shared implementation for an interface?

This irks me as I'm suffering multiple inheritance/mixin withdrawl.

Toy example:

public interface ITest
{
    List<string> TestList { get; }
}

public static class TestExtensions
{
    private const string Old = "Old";
    private const string New = "New";

    public static void ManipulateTestList(this ITest test)
    {
        for (int i = 0; i < test.TestList.Count; i++)
        {
            test.TestList[i] = test.TestList[i].Replace(Old, New);
        }
    }
}

public class Tester : ITest
{
    private List<string> testList = new List<string>();
    public List<string> TestList
    {
        get { return testList; }
    }

    public Tester()
    {
        testList.Add("OldOne");
        testList.Add("OldTwo");

        // Doesn't work
        // ManipulateTestList();

        // Works
        this.ManipulateTestList();
    } 
}
+1  A: 

Extension methods are a compiler trick that work on an object that redirect the call to a static method in another static class. 'this. is the object, that the compiler passes the static method. The non working example is simply the compiler telling you that the method is not and instance method scoped to the class.

Preet Sangha
+3  A: 

The relevant section in the language specification says:

7.6.5.2 Extension method invocations

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. If expr or any of the args has compile-time type dynamic, extension methods will not apply.

This clearly says that extension methods can only be invoked on an expression (expr). This expression can, of course, be “this”, but it must be present.

Timwi
I understand the mechanism, that's not the question. I'm interested in knowing why identifier() isn't in the list of allowed invocations. Would it cause pathological side effects when processing other constructs? Would it cause performance issues? Is it by design? etc.
LukeN
+2  A: 

I asked this exact question to the language team directly. I don't have the e-mail to hand, but basically the answer (from Mads, IIRC) was that it is:

  • to reduce the search-space / complexity - i.e. not having to consider all available extension methods (and prune them) unless there is the expression first.
  • to reduce the chance of an extension method "taking over" a regular method (i.e. being a better match) unexpectedly

Personally, I'd have liked it to work consistently - the first doesn't seem a big problem (but then, I don't write compilers), and neither approaches the fact that normally this.* is an optional thing (that may have influences such as local code style guidelines, i.e. "thou shalt use this.").

Marc Gravell
How much extra complexity does it add? I would have thought that the X() could be syntax-sugared into this.X() fairly simply. I understand it increases the time to report an invocation on method that doesn't exist, but I wouldn't expect it to take so long that it's unreasonable. And I would have thought the taking over problem could be solved by different abstract syntax tree node types. If you find the email, I'd love to see it.
LukeN
@LukeN - the AST is irrelevent if the spec says the extension method should be preferred. And I can't comment on the complexity, as I'm not a compiled writer ;p I'm with you: IMO it should be the same with/without the `this.`.
Marc Gravell