Почему невозможно объявить методы расширения в общем статическом классе? - PullRequest
20 голосов
/ 11 апреля 2010

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

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

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

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

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

Ответы [ 6 ]

14 голосов
/ 12 апреля 2010

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

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
    }
  }
3 голосов
/ 12 апреля 2010

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

Допустим, вы определили оба описанных вами метода:

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

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

2 голосов
/ 12 апреля 2010

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

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

1 голос
/ 12 апреля 2010

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

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

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

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

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

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

Напоминает мне о специализации шаблонов в C ++. РЕДАКТИРОВАТЬ: К сожалению, это не так, пожалуйста, смотрите мои дополнения ниже.


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

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 или в любом случае). Таким образом, единственная причина, по которой я могу придумать, заключается в том, что дизайнеры языка хотят поощрять вас к написанию расширений общего характера только по-настоящему общим способом.

Здесь могут произойти некоторые интересные вещи, если мы примем ко / контрвариантность к уравнению, которое собирается ввести в C # 4.0.

0 голосов
/ 14 апреля 2014

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

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

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

0 голосов
/ 11 апреля 2010

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

...