Подтвердить что ты не робот

Почему невозможно объявить методы расширения в общем статическом классе?

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

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

Я получаю "Методы расширения могут быть объявлены только в не общий, не вложенный статический класс".

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

Скажем, вы определяете оба метода, которые вы описываете:

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

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

Ответ 3

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

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

Ответ 4

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

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

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

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

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

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

Напоминает мне о специализации шаблона в С++. 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");
    }
}

Это произойдет с ошибкой компилятора:

Введите 'ConsoleApplication1.Test' уже определяет член, называемый "DoSomething" с тем же параметром Типы

Итак, затем мы попробуем разбить его на два разных класса расширений:

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

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

Некоторые интересные вещи могут произойти здесь, если мы возьмем co/contravariance in к уравнению, которое должно быть введено с С# 4.0.

Ответ 5

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

Ответ 6

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

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

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