Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I call an extension method from a base class of the extended type‏?

Tags:

I'm trying add the ability to lookup elements in a List<KeyValuePair<string,int>> by overriding the indexer.

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;  namespace ConsoleApplication2 {     public class MyList : List<KeyValuePair<string, int>>     {         public int this[string key]         {             get             {                 return base.Single(item => item.Key == key).Value;             }         }     } } 

For some reason, the compiler is throwing this error:

'System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string,int>>' does not contain a definition for 'Single'.

While it is true that List<T> doesn't have that method, it should be visible because it is an extension method from the System.Linq namespace (which is included). Obviously using this.Single resolves the issue, but why is access via base an error?

Section 7.6.8 of the C# spec says

When base.I occurs in a class or struct, I must denote a member of the base class of that class or struct.

Which might seem to preclude access to extension method via base. However it also says

At binding-time, base-access expressions of the form base.I and base[E] are evaluated exactly as if they were written ((B)this).I and ((B)this)[E], where B is the base class of the class or struct in which the construct occurs. Thus, base.I and base[E] correspond to this.I and this[E], except this is viewed as an instance of the base class.

If base.I is just like ((B)this).I then it seems like extension methods should be allowed here.

Can anyone explain the apparent contradiction in these two statements?

like image 202
synepis Avatar asked Jan 11 '15 02:01

synepis


People also ask

Can we define extension method for a class?

Extension methods are defined as static methods but are called by using instance method syntax. Their first parameter specifies which type the method operates on. The parameter is preceded by the this modifier.

Why extension methods are static?

Essentially, an extension method is a special type of a static method and enable you to add functionality to an existing type even if you don't have access to the source code of the type. An extension method is just like another static method but has the “this” reference as its first parameter.

Can you add extension methods to an existing static class?

The main advantage of the extension method is to add new methods in the existing class without using inheritance. You can add new methods in the existing class without modifying the source code of the existing class. It can also work with sealed class.

Can we add extension method to sealed class?

Sealed classes are used to restrict the users from inheriting the class. A class can be sealed by using the sealed keyword. The keyword tells the compiler that the class is sealed, and therefore, cannot be extended. No class can be derived from a sealed class.


1 Answers

Consider this situation:

public class Base {     public void BaseMethod()     {      } }  public class Sub : Base {     public void SubMethod()     {      } }  public static class Extensions {     public static void ExtensionMethod(this Base @base) { } } 

Here are some interesting assertions about this code:

  • I cannot call the extension method using ExtensionMethod() from neither Base nor Sub.
  • I cannot call base.ExtensionMethod() from Sub.
  • I can call the extension method using Extensions.ExtensionMethod(this) from both Sub and Base.
  • I can call the extension method using this.ExtensionMethod() from both Sub and Base.

Why is this?

I don't have a conclusive answer, partly because there might not be one: as you can read in this thread, you have to add this. if you want to call it in the extension method style.

When you're trying to use an extension method from the type it is in (or - consequently - from a type that is derived from the type used in the extension method), the compiler doesn't realize this and will try to call it as a static method without any arguments.

As the answer states: they [the language designers] felt it was not an important use case scenario to support implicit extension methods (to give the beast a name) from within the type because it would encourage extension methods that really should be instance methods and it was considered plain unnecessary.

Now, it is hard to find out what is happening exactly under the covers but from some playing around we can deduce that base.X() does not help us. I can only assume that base.X performs its virtual call as X() and not this.X() from the context of the baseclass.

What do I do when I want to call the extension method of a baseclass from a subclass?

Frankly, I haven't found any truly elegant solution. Consider this scenario:

public class Base {     protected void BaseMethod()     {         this.ExtensionMethod();     } }  public class Sub : Base {     public void SubMethod()     {         // What comes here?     } }  public static class Extensions {     public static void ExtensionMethod(this Base @base)      {          Console.WriteLine ("base");     }      public static void ExtensionMethod(this Sub sub)      {         Console.WriteLine ("sub");     } } 

There are 3 ways (leaving aside reflection) to call the ExtensionMethod(Base) overload:

  • Calling BaseMethod() which forms a proxy between the subclass and the extensionmethod.

You can use BaseMethod(), base.BaseMethod() and this.BaseMethod() for this since now you're just dealing with a normal instance method which in its turn will invoke the extension method. This is a fairly okay solution since you're not polluting the public API but you also have to provide a separate method to do something that should have been accessible in the context in the first place.

  • Using the extension method as a static method

You can also use the primitive way of writing an extension method by skipping the syntactic sugar and going straight to what it will be compiled as. Now you can pass in a parameter so the compiler doesn't get all confused. Obviously we'll pass a casted version of the current instance so we're targetting the correct overload:

Extensions.ExtensionMethod((Base) this); 
  • Use the - what should be identical translation - of base.ExtensionMethod()

This is inspired by @Mike z's remark about the language spec which says the following:

At binding-time, base-access expressions of the form base.I and base[E] are evaluated exactly as if they were written ((B)this).I and ((B)this)[E], where B is the base class of the class or struct in which the construct occurs. Thus, base.I and base[E] correspond to this.I and this[E], except this is viewed as an instance of the base class.

The spec literally says that base.I will be invoked as ((B) this).I. However in our situation, base.ExtensionMethod(); will throw a compilation error while ((Base) this).ExtensionMethod(); will work perfectly.

It looks like something is wrong either in the documentation or in the compiler but that conclusion should be drawn by someone with deeper knowledge in the matter (paging Dr. Lippert).

Isn't this confusing?

Yes, I would say it is. It kind of feels like a black hole within the C# spec: practically everything works flawlessly but then suddenly you have to jump through some hoops because the compiler doesn't know to inject the current instance in the method call in this scenario.

In fact, intellisense is confused about this situation as well:

enter image description here

We have already determined that that call can never work, yet intellisense believes it might. Also notice how it adds "using PortableClassLibrary" behind the name, indicating that a using directive will be added. This is impossible because the current namespace is in fact PortableClassLibrary. But of course when you actually add that method call:

enter image description here

and everything doesn't work as expected.

Perhaps a conclusion?

The main conclusion is simple: it would have been nice if this niche usage of extension methods would be supported. The main argument for not implementing it was because it would encourage people to write extension methods instead of instance methods.

The obvious problem here is of course that you might not always have access to the base class which makes extension methods a must but by the current implementation it is not possible.

Or, as we've seen, not possibly with the cute syntax.

like image 196
Jeroen Vannevel Avatar answered Nov 04 '22 10:11

Jeroen Vannevel