views:

403

answers:

5

I'd like to create a lot of extension methods for some generic class, e.g. for

public class SimpleLinkedList<T> where T:IComparable

And I've started creating methods like this:

public static class LinkedListExtensions
{
    public static T[] ToArray<T>(this SimpleLinkedList<T> simpleLinkedList) where T:IComparable
    {
       //// code
    }
}

But when I tried to make LinkedListExtensions class generic like this:

public static class LinkedListExtensions<T> where T:IComparable
{
    public static T[] ToArray(this SimpleLinkedList<T> simpleLinkedList)
    {
         ////code
    }
}

I get "Extension methods can only be declared in non-generic, non-nested static class".

And I'm trying to guess where this restriction came from and have no ideas.

EDIT: Still don't have clear vision of the problem. It seems like this was just not implemented for some reason.

A: 

Just a thought, but is there any reason you can't just derive from this class and add the extra methods to the specialization rather than writing a suite of extension methods? I am sure you have your reasons but just throwing that out there.

Steve Sheldon
+1  A: 

A very interesting question, I have never been tempted to use static generic classes, but at least it seems possible.

In the context of declaring extension methods, you cannot only declare extension methods for a certain generic type (such as IEnumerable<T>) but also bring the type parameter T into the equation. If we agree to treat IEnumerable<int> and IEnumerable<string> as different types, this makes sense on a conceptual level too.

Being able to declare your extension methods in a static generic class would save you repeating your type parameter constraints over and over again, in effect grouping all extension methods for IEnumerable<T> where T : IComparable together.

According to the specification (citation needed), extension methods can only be declared in static not-nested and not-generic classes. The reason for the first two constraints are fairly obvious:

  1. May not carry any state as it's not a mixin, just syntactic sugar.
  2. Must have the same lexical scope as the type it provides extensions for.

The restriction on non-generic static classes seems a little arbitrary to me, I can't come up with a technical reason here. But it might be that the language designers decided to discourage you in writing extension methods that depend on the type parameter of a generic class you wish to provide extension methods for. Instead they want you to provide a truly generic implementation of your extension method but make it possible to provide optimized/specialized (compile-time bound) versions of your extension methods in addition to your general implementation.

Reminds me of template specialization in C++. EDIT: Unfortunately this is wrong, please see my additions below.


Ok, as this is a really interesting topic I did some further research. There actually is a technical restriction that I missed here. Let's look at some code:

public static class Test
{
    public static void DoSomething<T>(this IEnumerable<T> source)
    {
        Console.WriteLine("general");
    }
    public static void DoSomething<T>(this IEnumerable<T> source) where T :IMyInterface
    {
        Console.WriteLine("specific");
    }
}

This will actually fail with this compiler error:

Type 'ConsoleApplication1.Test' already defines a member called 'DoSomething' with the same parameter types

Ok, next we try splitting it up into two different extensions classes:

public interface IMyInterface { }
public class SomeType : IMyInterface {}

public static class TestSpecific
{
    public static void DoSomething<T>(this IEnumerable<T> source) where T : IMyInterface 
    {
        Console.WriteLine("specific");
    }
}
public static class TestGeneral
{
    public static void DoSomething<T>(this IEnumerable<T> source)
    {
        Console.WriteLine("general");
    }
}

class Program
{
    static void Main(string[] args)
    {
        var general = new List<int>();
        var specific = new List<SomeType>();

        general.DoSomething();
        specific.DoSomething();

        Console.ReadLine();
    }
}

Against my initial impression (it was late yesterday night), this will result in an ambiguity at the call sites. To resolve this ambiguity one would need to call the extension method in a traditional way, but that's against our intention.

So this leaves us with a situation where it's not possible to declare compile-time bound generic specializations of extension methods. On the other hand, there is still no reason why we couldn't declare extension methods only for a single special generic type parameter. Therefore it'd be nice to declare them in a static generic class.

On the other hand writing an extension method like:

    public static void DoSomething<T>(this IEnumerable<T> source) where T : IMyInterface {}

or

    public static void DoSomething(this IEnumerable<IMyInterface> source) {}

is not all too different and only requires a little casting on the call site vs. on the extension method side (you will probably implement some optimization that depends on the specific type, so you'd need to cast T to IMyInterface or whatever anyway). So the only reason I can come up with is again, the language designers want to encourage you in writing generic extensions only in a truly generic fashion.

Some interesting things could happen here if we take co/contravariance in to the equation, which are about to be introduced with C# 4.0.

Johannes Rudolph
+3  A: 

The problem is how does the compiler do the extension resolution?

Say you define both the methods you describe:

public static class LinkedListExtensions {
    public static T[] ToArray<T>(this SimpleLinkedList<T> simpleLinkedList) where T:IComparable {
        //// code
    }
}
public static class LinkedListExtensions<T> where T:IComparable {
    public static T[] ToArray(this SimpleLinkedList<T> simpleLinkedList) {
        ////code
    }
}

Which method is used in the following case?

SimpleLinkedList<int> data = new SimpleLinkedList<int>();
int[] dataArray = data.ToArray();

My guess is that the language designers decided to restrict extension methods to the non-generic types to avoid this scenario.

Cameron MacFarland
As far as my testing showed the compiler can resolve this! It has nothing to do with the static class being generic.
Johannes Rudolph
@Johannes Rudolph: And which method would it choose? It is not obvious to me at all.
Gorpik
@Johannes Rudolph - What test did you do? The original question was about how the second class in the example doesn't compile, so how did you compile this?
Cameron MacFarland
Good point but probably compiler should see this and say something like "Member with the same signature already declared".
Hun1Ahpu
@Gorpik, such conflicts also appear when having the same method in two classes. It is a mistery for me too why they forbade this, as is why the class must be static. But they chose to do so, and you have to live with it... Edit: Actually there is a reason to exlude generic extension methods, but an error about missing parameters would also suit, but be a bit messy.
Dykam
@Cameron: I'm sorry I was wrong here, I have done some further research. Looks very interesting. So far Gorpik's explanation seems very good for me, although I don't fully understand his examples.
Johannes Rudolph
+4  A: 

Generally speaking, since you do not specify the class when you use an extension method, the compiler would have no way to know which is the class where the extension method is defined:

static class GenStatic<T>
{
  static void ExtMeth(this Class c) {/*...*/}
}

Class c = new Class();
c.ExtMeth(); // Equivalent to GenStatic<T>.ExtMeth(c); what is T?

Since extension methods themselves can be generic, this is no real problem at all:

static class NonGenStatic
{
  static void GenExtMeth<T>(this Class c) {/*...*/}
}

Class c = newClass();
c.ExtMeth<Class2>(); // Equivalent to NonGenStatic.ExtMeth<Class2>(c); OK

You can easily rewrite your example so that the static class is not generic, but the generic methods are. In fact, this is how .NET classes such as Enumerable are written.

  public static class LinkedListExtensions
  {
    public static T[] ToArray(this SimpleLinkedList<T> where T:IComparable simpleLinkedList)
    {
      // code
    }
  }
Gorpik
Good point about the fact that non-generic extension methods on generic static classes would cause headaches.
Johannes Rudolph
+1  A: 

Don't think of extension methods as being tied to the static class in which they are contained. Instead, think of them as being tied to a specific namespace. Therefore, the static class in which they are defined is simply a shell used to declare these methods within the namespace. And while you could very well write multiple classes for different types of extension methods, you shouldn't think of the class itself as anything more than a way to clearly group your extension methods.

Extension methods do not extend any attributes of the class in which they are contained. The signature of the method will define everything about the extension method. And while you could also call your method like this, LinkedListExtensions.ToArray(...), I do not believe that was the intent for extension methods. As such, I believe the framework creators probably created the restriction you ran into as a way to inform developers simply that extension methods are self-contained and are not directly tied to the class in which they reside.

300 baud