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