Очень интересный вопрос, у меня никогда не было соблазна использовать статические универсальные классы, но, по крайней мере, это кажется возможным.
В контексте объявления методов расширения вы можете не только объявить методы расширения для определенного универсального типа (например, IEnumerable<T>
), но и ввести параметр типа T
в уравнение. Если мы согласны рассматривать IEnumerable<int>
и IEnumerable<string>
как разные типы, это имеет смысл и на концептуальном уровне.
Возможность объявлять ваши методы расширения в статическом универсальном классе избавит вас от повторения ограничений параметров типа снова и снова, по сути, сгруппировав все методы расширения для IEnumerable<T> where T : IComparable
вместе.
В соответствии со спецификацией (требуется цитирование), методы расширения могут быть объявлены только в статических не вложенных и не универсальных классах. Причина первых двух ограничений довольно очевидна:
- Может не иметь никакого состояния, поскольку это не миксин, а только синтаксический сахар.
- Должен иметь ту же лексическую область, что и тип, для которого он предоставляет расширения.
Ограничение на неуниверсальные статические классы мне кажется немного произвольным, здесь я не могу придумать техническую причину. Но может случиться так, что разработчики языка решили отговорить вас при написании методов расширения, которые зависят от параметра типа универсального класса, для которого вы хотите предоставить методы расширения. Вместо этого они хотят, чтобы вы предоставили действительно общую реализацию вашего метода расширения, но в то же время позволяют предоставлять оптимизированные / специализированные (с компиляцией во времени) версии ваших методов расширения в дополнение к общей реализации.
Напоминает мне о специализации шаблонов в 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.