views:

72

answers:

2

I've written dozens of extension methods and they all work as expected. But this is the first time I ran into using an extension method in this context.

public static class ControllerExtensions
{
    public static RedirectToRouteResult RedirectToAction<TController>(
        this Controller controller
        , Expression<Action<TController>> action
      ) where TController : Controller
    {
      RouteValueDictionary routeValuesFromExpression = 
        ExpressionHelper.GetRouteValuesFromExpression<TController>(action);

      return new RedirectToRouteResult(routeValuesFromExpression);
    }
}

Looks normal enough, right? But within my controllers, I cannot access this extension method by typing. Instead, I have to prefix it with the keyword "this". For example:

// This does not work, I get a compiler error because
// RedirectToAction has no overload for the generic.
//
return
  RedirectToAction<MembershipController>(
     c => c.RegisterSuccess(Server.UrlEncode(code) ));

// But, this does work?!?!
//
return
  this.RedirectToAction<MembershipController>(
     c => c.RegisterSuccess(Server.UrlEncode(code) ));

Very odd. Perhaps it is because I am within the instance object I am extending? The "controller" instance that is?

Sure enough, I was able to duplicate it in a simple console app:

class Program
{
    static void Main(string[] args)
    {
     var x = new TestClass();
     x.Go<String>();
    }
}

public class TestClass
{
    public void Go()
    {
    }

    public void NextMethod()
    {
     // compiler error.  :(
     Go<String>();

     // works!
     this.Go<String>();
    }
}

public static class TestExtension
{
    public static string Go<T>(this TestClass theClass)
    {
     return String.Empty;
    }
}

So why does 'this.' work?

+5  A: 

Extension methods aren't part of the "default" lookup for members - you have to be using an expression of the form Target.Method before extension methods are checked. this.Foo() conforms to that requirement, so it works.

From section 7.5.5.2:

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.

Admittedly all that says is "the compiler is following the spec" rather than the reason why the spec was written that way... I don't know whether there is any specific reason, although the fact that you can invoke both instance members and static members using just Method() (instead of specifying either an instance or a type) may be relevant.

Jon Skeet
You could argue, for example, that extension methods are supposed to support adding additional functionality to existing classes you don't control - if your object relied upon a particular extension method, then it should be built in to the main class definition, rather than rely upon a weak and potentially breakable dependency.
Rob
Thank you both for the reply and comment.
eduncan911
A: 

I think is because of how extension methods works.

When you write Go(), the compiler assumes that Go is a method in the current class, which isn't.

Extension methods are 'attached' to a instance, and you specify the instance using the this keyword.

Ricky AH
Thanks for your answer. Jon actually answered it correctly by specifying that the compiler is working from the spec in form of Target.Method.
eduncan911