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

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

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

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

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

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

20
задан Hun1Ahpu 15 April 2010 в 08:19
поделиться

5 ответов

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

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

Возможность объявить методы расширения в статическом родовом классе избавит вас от необходимости повторять ограничения на параметры типа снова и снова, фактически сгруппировав все методы расширения для IEnumerable, где 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");
    }
}

На самом деле это приведет к ошибке компилятора:

Тип '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();
    }
}

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

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

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

    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.

1
ответ дан 30 November 2019 в 01:13
поделиться

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

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

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
ответ дан 30 November 2019 в 01: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
    }
  }
14
ответ дан 30 November 2019 в 01:13
поделиться

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

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

2
ответ дан 30 November 2019 в 01:13
поделиться

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

0
ответ дан 30 November 2019 в 01:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: