2010-04-11 2 views
20

Я хотел бы создать много методов расширения для какого-то общего класса, например. дляПочему невозможно объявить методы расширения в общем статическом классе?

public class SimpleLinkedList<T> where T:IComparable 

И я начал создавать такие методы, как это:

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

Но когда я попытался сделать LinkedListExtensions класса родовым так:

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

я получаю «методы внутренней линии может только объявляется в неэквивалентном, не вложенном статическом классе ».

И я пытаюсь догадаться, откуда взялось это ограничение и нет идей.

РЕДАКТОР: Все еще не имеют четкого видения проблемы. Похоже, что это почему-то не было реализовано.

+0

@ Dan Bryant, статические общие классы разрешены. Только не для классов, содержащих методы расширения. – Dykam

+0

Помимо вопроса ... Почему в вашем первом примере .ToArray() определяется как метод расширения? Если это в классе SimpleLinkedList , вы можете просто определить общедоступный метод T [] ToArray(). –

+0

Возможно, чтобы упростить запись компиляторов (а не только компилятор C#), так как это уменьшает количество условий для проверки. –

ответ

0

Просто мысль, но есть ли причина, по которой вы не можете просто извлечь из этого класса и добавить дополнительные методы к специализации, а не писать набор методов расширения? Я уверен, что у вас есть свои причины, но просто выбрасываю это.

1

Очень интересный вопрос, я никогда не испытывал соблазна использовать статические родовые классы, но по крайней мере это кажется возможным.

В контексте объявления методов расширения вы можете не только объявлять методы расширения для определенного общего типа (например, IEnumerable<T>), но также вводить параметр типа T в уравнение. Если мы согласны рассматривать IEnumerable<int> и IEnumerable<string> как разные типы, это также имеет смысл на концептуальном уровне.

Возможность объявить ваши методы расширения в статическом универсальном классе позволит сэкономить время на повторение ограничений типа параметра снова и снова, в результате группируя все методы расширения для IEnumerable<T> where T : IComparable вместе.

Согласно спецификации (цитата), методы расширения могут быть объявлены только в статических не-вложенных и неэквивалентных классах. Причина первых двух ограничений довольно очевидна:

  1. Может не носить какое-либо состояние, так как это не микшин, а просто синтаксический сахар.
  2. Должен иметь такой же лексический охват, как и тип, для которого он предназначен.

Ограничение на неосновные статические классы представляется мне немного произвольным, я не могу придумать техническую причину здесь. Но может быть, разработчики языка решили отказать вам в написании методов расширения, которые зависят от параметра типа общего класса, для которого вы хотите предоставить методы расширения. Вместо этого они хотят, чтобы вы предоставили поистине универсальную реализацию вашего метода расширения, но предоставили возможность дополнительно оптимизировать/специализировать (связанные с компиляцией) версии ваших методов расширения в дополнение к вашей общей реализации.

Напоминает мне о специализации шаблона на C++. EDIT: К сожалению, это неправильно, см. Мои добавления ниже.


Хорошо, поскольку это действительно интересная тема, я сделал несколько дальнейших исследований.На самом деле есть технические ограничения, которые я пропустил здесь. Давайте посмотрим на код:

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"); 
    } 
} 

Это фактически потерпит неудачу с этой ошибкой компилятора:

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

Ok, рядом мы пытаемся разделив его на два различных классов расширений:

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(); 
    } 
} 

Против моего первоначального впечатления (вчера было поздно), это приведет к двусмысленности на сайтах. Чтобы устранить эту двусмысленность, нужно было бы вызвать метод расширения традиционным способом, но это противоречит нашему намерению.

Таким образом, это оставляет нам ситуацию, когда невозможно объявить обобщенные обобщенные специализации методов расширения. С другой стороны, по-прежнему нет причин, по которым мы не могли объявить методы расширения только для одного специального типового параметра типа. Поэтому было бы неплохо объявить их в статическом родовом классе.

С другой стороны, писать метод расширения как:

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

или

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

не слишком отличается и требует лишь небольшой кастинг на VS. места вызова на расширение (вы, вероятно, будете реализовывать некоторую оптимизацию, зависящую от определенного типа, поэтому вам нужно будет отбрасывать T на IMyInterface или что-то в любом случае). Поэтому единственная причина, по которой я могу придумать, - опять же, разработчики языка хотят поощрять вас писать общие расширения только по-настоящему универсальным способом.

Здесь может произойти некоторое интересное, если мы возьмем co/contravariance в уравнение, которое должно быть введено с C# 4.0.

3

Проблема заключается в том, как компилятор делает разрешение расширения?

Допустим, вы определить как методы, которые описывают:

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 
    } 
} 

Какой метод используется в следующем случае?

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

Я предполагаю, что разработчики языка решили ограничить методы расширения не-генерическими типами, чтобы избежать этого сценария.

+0

Насколько я могу проверить, компилятор может решить эту проблему! Это не имеет никакого отношения к тому, что статический класс является общим. –

+0

@Johannes Rudolph: И какой метод выбрать? Для меня это совершенно не очевидно. – Gorpik

+0

@Johannes Rudolph - Какой тест вы сделали? Первоначальный вопрос был о том, как второй класс в примере не компилируется, так как вы его скомпилировали? –

13

Вообще говоря, так как вы не указали класс при использовании методы расширения, компилятор не будет иметь никакого способа узнать, что класс, где определен метод расширения:

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? 

Поскольку методы расширения сами по себе могут быть универсальными, это не реальная проблема вообще:

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

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

Вы можете легко переписать пример так, что статический класс не является универсальным, но общие методы. На самом деле, так написаны классы .NET, такие как Enumerable.

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

Хороший вопрос о том, что не общие методы расширения на общих статических классах могут вызвать головные боли. –

+0

Если метод расширения не использовал статические члены класса, в котором он содержится, не имеет значения, какой класс был выбран. Вы можете увеличить свой пример, если метод расширения использует нечто вроде поля статического класса; выбор поля, очевидно, будет семантически релевантным, но компилятор не сможет выбрать его. – supercat

+0

@supercat: Это не имеет значения. Компилятор не может выбрать только любой класс: он должен знать, какой конкретный класс выбрать, даже если реализация метода точно такая же для всех из них. – Gorpik

2

Не думайте, что методы расширения привязаны к статическому классу, в котором они содержатся. Вместо этого подумайте, что они привязаны к определенному пространству имен. Поэтому статический класс, в котором они определены, является просто оболочкой, используемой для объявления этих методов в пространстве имен. И хотя вы можете очень хорошо писать несколько классов для разных типов методов расширения, вы не должны думать о самом классе как о чем-то более, чем о том, как четко группировать ваши методы расширения.

Методы расширения не распространяют никаких атрибутов класса, в котором они содержатся. Подпись метода будет определять все о методе расширения. И хотя вы можете также назвать свой метод следующим образом: LinkedListExtensions.ToArray(...), я не считаю, что это было предназначено для методов расширения. Таким образом, я считаю, что создатели фреймворка, вероятно, создали ограничение, с которым вы столкнулись, как способ информировать разработчиков просто о том, что методы расширения являются автономными и напрямую не связаны с классом, в котором они находятся.

-1

Статические классы должны быть определены как абстрактные. Если бы они могли это сделать, то вы могли бы указать, что их нельзя использовать напрямую, но должны быть унаследованы точно так же, как обычный класс, а затем общие статические классы выполняются для методов расширения, потому что вы можете определить их в абстрактном классе, наследовать от указанного класса, и все будет работать должным образом.

Как сейчас, существует множество значительных ограничений со статическими классами, это просто то, что действительно во многом связано с mojo. В сочетании с невозможностью компилятора вывести тип возврата и, следовательно, потребовать, чтобы вы ввели всю общую реализацию, чтобы вызвать общие функции, которые не имеют всех генериков в подписи метода, делает этот материал усугубленным слабым и делает много который не должен быть там.

(Существует также случай, когда второй родословный определяется в определении первого, и он не будет его использовать, хотя очевидно, что и во время компиляции очевидно, что это очень раздражает, потому что это совершенно очевидно.) MS имеет TON работы над Generics, что они могли бы сделать, чтобы улучшить этот материал.