Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Замечание
Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Этот документ включает предлагаемые изменения спецификации, а также информацию, необходимую во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.
Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия отражены в соответствующих заметках с заседания по дизайну языка (LDM) .
Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .
Вопрос чемпиона: https://github.com/dotnet/csharplang/issues/8697
Декларация
Синтаксис
class_body
: '{' class_member_declaration* '}' ';'?
| ';'
;
class_member_declaration
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| operator_declaration
| constructor_declaration
| finalizer_declaration
| static_constructor_declaration
| type_declaration
| extension_declaration // add
;
extension_declaration // add
: 'extension' type_parameter_list? '(' receiver_parameter ')' type_parameter_constraints_clause* extension_body
;
extension_body // add
: '{' extension_member_declaration* '}' ';'?
;
extension_member_declaration // add
: method_declaration
| property_declaration
| operator_declaration
;
receiver_parameter // add
: attributes? parameter_modifiers? type identifier?
;
Объявления расширений объявляются только в неуниверсальных, невложенных статических классах.
Это ошибка для типа, который будет называться extension.
Правила области
Параметры типа и параметр приемника объявления расширения находятся в области видимости внутри тела объявления расширения. Это ошибка ссылаться на параметр приемника из статического члена, за исключением выражения nameof. Объявление членами параметров типа или параметров (а также локальных переменных и функций внутри тела метода) с тем же именем, что и параметр типа или параметр получателя в объявлении расширения, является ошибкой.
public static class E
{
extension<T>(T[] ts)
{
public bool M1(T t) => ts.Contains(t); // `T` and `ts` are in scope
public static bool M2(T t) => ts.Contains(t); // Error: Cannot refer to `ts` from static context
public void M3(int T, string ts) { } // Error: Cannot reuse names `T` and `ts`
public void M4<T, ts>(string s) { } // Error: Cannot reuse names `T` and `ts`
}
}
Это не является ошибкой для самих членов иметь то же имя, что и параметры типа или параметр приемника во включающем объявлении расширения. Имена членов не находятся напрямую при простом поиске имен в объявлении расширения; следовательно, поиск находит параметр типа или параметр приемника этого имени, а не члена.
Члены приводят к тому, что статические методы объявляются непосредственно в закрытом статическом классе, и их можно найти с помощью простого поиска имен; Однако сначала будет найден параметр типа объявления расширения или параметр приемника с тем же именем.
public static class E
{
extension<T>(T[] ts)
{
public void T() { M(ts); } // Generated static method M<T>(T[]) is found
public void M() { T(ts); } // Error: T is a type parameter
}
}
Статические классы в качестве контейнеров расширений
Расширения объявляются внутри не универсальных статических классов верхнего уровня, как и методы расширения сегодня, и таким образом могут сосуществовать с классическими методами расширения и несовершенными статическими элементами:
public static class Enumerable
{
// New extension declaration
extension(IEnumerable source) { ... }
// Classic extension method
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) { ... }
// Non-extension member
public static IEnumerable<int> Range(int start, int count) { ... }
}
Объявления расширения
Объявление расширения является анонимным и предоставляет спецификацию приемника с любыми связанными параметрами типов и ограничениями, после чего следует набор объявлений членов расширения. Спецификация приемника может находиться в виде параметра или , если объявляются только статические члены расширения - тип:
public static class Enumerable
{
extension(IEnumerable source) // extension members for IEnumerable
{
public bool IsEmpty { get { ... } }
}
extension<TSource>(IEnumerable<TSource> source) // extension members for IEnumerable<TSource>
{
public IEnumerable<T> Where(Func<TSource, bool> predicate) { ... }
public IEnumerable<TResult> Select<TResult>(Func<TSource, TResult> selector) { ... }
}
extension<TElement>(IEnumerable<TElement>) // static extension members for IEnumerable<TElement>
where TElement : INumber<TElement>
{
public static IEnumerable<TElement> operator +(IEnumerable<TElement> first, IEnumerable<TElement> second) { ... }
}
}
Тип в спецификации приемника называется типом приемника и именем параметра, если он присутствует, называется параметром приемника.
Если параметр приемника называется, тип приемника может не быть статическим.
Параметру приемника не разрешено иметь модификаторы, если он не имеет имени, и разрешено использовать только модификаторы ссылочности, указанные ниже или scoped, в иных случаях.
Параметр приемника имеет те же ограничения, что и первый параметр классического метода расширения.
Атрибут [EnumeratorCancellation] игнорируется, если он помещается в параметр приемника.
Члены расширения
Объявления членов расширения синтаксически идентичны соответствующим экземплярам и статическим элементам в объявлениях классов и структур (за исключением конструкторов). Члены экземпляра ссылаются на приемник с именем параметра получателя:
public static class Enumerable
{
extension(IEnumerable source)
{
// 'source' refers to receiver
public bool IsEmpty => !source.GetEnumerator().MoveNext();
}
}
Ошибка при указании члена расширения экземпляра, если включающее объявление расширения не указывает параметр приемника:
public static class Enumerable
{
extension(IEnumerable) // No parameter name
{
public bool IsEmpty => true; // Error: instance extension member not allowed
}
}
Это ошибка указать следующие модификаторы для элемента объявления расширения: abstract, virtual, override, new, sealed, partial, protected и связанные модификаторы доступности.
Ошибка в указании readonly модификатора для члена объявления расширения.
Свойства в объявлениях расширения не могут иметь методов доступа, таких как init.
Экземплярные члены запрещены, если параметр приемника не имеет имени.
Все члены должны иметь имена, отличающиеся от имени статического включающего класса и имени расширенного типа, если он имеет один.
Это ошибка декорирования элемента расширения атрибутом [ModuleInitializer] .
Refness
По умолчанию получатель передается членам расширения экземпляра по значению, как и другие параметры.
Однако получатель объявления расширения в форме параметра может указывать refи ref readonlyin, если тип приемника, как известно, является типом значения.
Возможность null и атрибуты
Типы приемников могут быть или содержать ссылочные типы, допускающие значение NULL, и спецификации приемника, которые находятся в форме параметров, могут указывать атрибуты:
public static class NullableExtensions
{
extension(string? text)
{
public string AsNotNull => text is null ? "" : text;
}
extension([NotNullWhen(false)] string? text)
{
public bool IsNullOrEmpty => text is null or [];
}
extension<T> ([NotNull] T t) where T : class?
{
public void ThrowIfNull() => ArgumentNullException.ThrowIfNull(t);
}
}
Совместимость с классическими методами расширения
Методы расширения экземпляра создают артефакты, такие же, как те, что создаются при помощи традиционных методов расширения.
В частности, созданный статический метод содержит атрибуты, модификаторы и имя объявленного метода расширения, а также список параметров типа, список параметров и ограничения, объединенные из объявления расширения и объявления метода в этом порядке:
public static class Enumerable
{
extension<TSource>(IEnumerable<TSource> source) // Generate compatible extension methods
{
public IEnumerable<TSource> Where(Func<TSource, bool> predicate) { ... }
public IEnumerable<TSource> Select<TResult>(Func<TSource, TResult> selector) { ... }
}
}
Generates:
[Extension]
public static class Enumerable
{
[Extension]
public static IEnumerable<TSource> Where<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate) { ... }
[Extension]
public static IEnumerable<TSource> Select<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult> selector) { ... }
}
Операторы
Хотя операторы расширения имеют явные типы операндов, они по-прежнему должны быть объявлены в объявлении расширения:
public static class Enumerable
{
extension<TElement>(IEnumerable<TElement>) where TElement : INumber<TElement>
{
public static IEnumerable<TElement> operator *(IEnumerable<TElement> vector, TElement scalar) { ... }
public static IEnumerable<TElement> operator *(TElement scalar, IEnumerable<TElement> vector) { ... }
}
}
Это позволяет объявлять и выводить параметры типа и аналогично тому, как обычный определяемый пользователем оператор должен быть объявлен в одном из его типов операнда.
Checking
Выводимость: Для каждого члена расширения, отличного от метода, все параметры типа своего блока расширения должны использоваться в объединенном наборе параметров из расширения и члена.
Уникальность: В рамках заданного заключающего статического класса набор объявлений членов расширения с тем же типом приемника (преобразованием удостоверений модуло и подстановкой имени параметра типа) рассматриваются как одно пространство объявлений, аналогичное элементам в объявлении класса или структуры, и подчиняются тем же правилам об уникальности.
public static class MyExtensions
{
extension<T1>(IEnumerable<int>) // Error! T1 not inferrable
{
...
}
extension<T2>(IEnumerable<T2>)
{
public bool IsEmpty { get ... }
}
extension<T3>(IEnumerable<T3>?)
{
public bool IsEmpty { get ... } // Error! Duplicate declaration
}
}
Применение этого правила уникальности включает в себя классические методы расширения в одном и том же статическом классе.
Для сравнения с методами в объявлениях this расширений параметр рассматривается как спецификация приемника вместе с любыми параметрами типа, упомянутыми в этом типе приемника, а остальные параметры типа и параметры метода используются для подписи метода:
public static class Enumerable
{
public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) { ... }
extension(IEnumerable source)
{
IEnumerable<TResult> Cast<TResult>() { ... } // Error! Duplicate declaration
}
}
Потребление
При попытке поиска члена расширения все объявления расширений в статических классах, usingимпортируемые в качестве кандидатов, независимо от типа приемника. Только в рамках разрешения кандидаты с несовместимыми типами приемников отменены.
Выполняется попытка полного вывода универсального типа между типом аргументов (включая фактического получателя) и любыми параметрами типа (сочетая их в объявлении расширения и в объявлении члена расширения).
Если указаны явные аргументы типа, они используются для замены параметров типа объявления расширения и объявления члена расширения.
string[] strings = ...;
var query = strings.Select(s => s.Length); // extension invocation
var query2 = strings.Select<string, int>(s => s.Length); // ... with explicit full set of type arguments
var query3 = Enumerable.Select(strings, s => s.Length); // static method invocation
var query4 = Enumerable.Where<string, int>(strings, s => s.Length); // ... with explicit full set of type arguments
public static class Enumerable
{
extension<TSource>(IEnumerable<TSource> source)
{
public IEnumerable<TResult> Select<TResult>(Func<T, TResult> predicate) { ... }
}
}
Аналогично классическим методам расширения, создаваемые методы реализации можно вызывать статически.
Это позволяет компилятору различать членов расширения с одинаковым именем и арностью.
object.M(); // ambiguous
E1.M();
new object().M2(); // ambiguous
E1.M2(new object());
_ = _new object().P; // ambiguous
_ = E1.get_P(new object());
static class E1
{
extension(object)
{
public static void M() { }
public void M2() { }
public int P => 42;
}
}
static class E2
{
extension(object)
{
public static void M() { }
public void M2() { }
public int P => 42;
}
}
Статические методы расширения будут обрабатываться подобно методам расширения экземпляра (мы будем учитывать дополнительный аргумент типа приемника).
Свойства расширения обрабатываются аналогично методам расширения, с одним параметром (параметром приемника) и одним аргументом (фактическим значением приемника).
Директивы using static
Using_static_directive делает члены блоков расширений в объявлении типа доступными для доступа к расширению.
using static N.E;
new object().M();
object.M2();
_ = new object().Property;
_ = object.Property2;
C c = null;
_ = c + c;
c += 1;
namespace N
{
static class E
{
extension(object o)
{
public void M() { }
public static void M2() { }
public int Property => 0;
public static int Property2 => 0;
}
extension(C c)
{
public static C operator +(C c1, C c2) => throw null;
public void operator +=(int i) => throw null;
}
}
}
class C { }
Как и раньше, доступные статические члены (кроме методов расширения), содержащиеся непосредственно в объявлении данного типа, можно ссылаться напрямую.
Это означает, что методы реализации (за исключением методов расширения) можно использовать непосредственно в качестве статических методов:
using static E;
M();
System.Console.Write(get_P());
set_P(43);
_ = op_Addition(0, 0);
_ = new object() + new object();
static class E
{
extension(object)
{
public static void M() { }
public static int P { get => 42; set { } }
public static object operator +(object o1, object o2) { return o1; }
}
}
Using_static_directive по-прежнему не импортирует методы расширения непосредственно как статические методы, поэтому метод реализации для методов расширения, не являющихся статическими, нельзя вызывать непосредственно в качестве статического метода.
using static E;
M(1); // error: The name 'M' does not exist in the current context
static class E
{
extension(int i)
{
public void M() { }
}
}
Приоритет разрешения перегрузки Attribute
Члены расширения в заключенном статичном классе подвергаются приоритизации в соответствии со значениями ORPA. Объявленный статический класс считается "содержащим типом", с которыми работают правила ORPA.
Любой атрибут ORPA, присутствующий в свойстве расширения, копируется в методы реализации аксессоров свойства, чтобы приоритезация соблюдалась, когда эти аксессоры используются с помощью синтаксиса дизамбигуации.
Точки входа
Методы блоков расширений не соответствуют кандидатам в точки входа (см. "7.1 Запуск приложения"). Примечание. Метод реализации по-прежнему может быть кандидатом.
Снижение
Стратегия снижения объявлений расширений не является решением уровня языка. Однако помимо реализации семантики языка он должен соответствовать определенным требованиям:
- Формат созданных типов, элементов и метаданных должен быть четко указан во всех случаях, чтобы другие компиляторы могли использовать и создавать их.
- Созданные артефакты должны быть стабильными, в том смысле, что разумные последующие изменения не должны нарушать потребителей, скомпилированных против более ранних версий.
Эти требования требуют большего уточнения по мере прогресса реализации и могут быть скомпрометированы в угловых случаях, чтобы обеспечить разумный подход к реализации.
Метаданные для объявлений
Цели
Приведенный ниже дизайн позволяет:
- округление символов объявления расширения с помощью метаданных (полных и ссылочных сборок),
- стабильные ссылки на члены расширения (XML-документы)
- локальное определение выдаваемых имен (полезно для EnC),
- отслеживание общедоступного интерфейса программирования приложений (API).
Для xml-доков docID члена расширения — это docID этого члена в метаданных. Например, идентификатор docID, используемый в cref="Extension.extension(object).M(int)", — M:Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32), и этот docID остается стабильным в процессе повторной компиляции и при любом порядке блоков расширений. В идеале он также останется стабильным, когда ограничения на блок расширения изменяются, но мы не нашли дизайн, который позволяет достичь этого без ущерба для языка для разрешения конфликтов членов.
Для EnC полезно знать локально (просто глядя на измененный член расширения), где обновленный член расширения записывается в метаданные.
Для отслеживания общедоступных API более стабильные имена снижают шум. Однако с технической точки зрения, названия типов группировки расширений не должны вмешиваться в подобные сценарии. При рассмотрении члена M расширения не имеет значения название типа группы расширений, важно то, к какой сигнатуре блока расширений он принадлежит. Подпись общедоступного API не должна рассматриваться как Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32) , а как Extension.extension(object).M(int). Другими словами, члены расширения должны рассматриваться как имеющие два набора параметров типа и два набора параметров.
Обзор
Блоки расширений группируются по сигнатуре уровня CLR. Каждая группа эквивалентности CLR излучается как тип группировки расширений с именем, основанным на содержимом. Блоки расширения в группе эквивалентности CLR затем подразделяются по эквивалентности C#. Каждая группа эквивалентности C# создается как тип маркера расширения с именем на основе содержимого, вложенным в соответствующий тип группирования расширений. Тип маркера расширения содержит один метод маркера расширения , который кодирует параметр расширения. Метод маркера расширения вместе с типом, к которому он относится, кодирует подпись блока расширения с полной точностью. Объявление каждого члена расширения создается в правильном типе группировки расширений, ссылается на тип маркера расширения по имени через атрибут и сопровождается методом статической реализации верхнего уровня с измененной сигнатурой.
Ниже приведены схематизированные общие сведения о кодировке метаданных:
[Extension]
static class EnclosingStaticClass
{
[Extension]
public sealed class ExtensionGroupingType1 // has type parameters with minimal constraints sufficient to keep extension member declarations below valid
{
public static class ExtensionMarkerType1 // has re-declared type parameters with full fidelity of C# constraints
{
public static void <Extension>$(... extension parameter ...) // extension marker method
}
... ExtensionMarkerType2, etc ...
... extension members for ExtensionGroupingType1, each points to its corresponding extension marker type ...
}
... ExtensionGroupingType2, etc ...
... implementation methods ...
}
Охватывающий статический класс создается с атрибутом [Extension].
Сигнатура уровня CLR и подпись на уровне C#
Сигнатура уровня CLR блока расширения приводит к следующим результатам:
- нормализация имен параметров типа на
T0,T1, и т. д. - удаление атрибутов
- удаление имени параметра
- модификаторы параметров стирания (например
ref, ,inscoped, ...) - стирание имен кортежей
- удаление аннотаций nullability
- удаление ограничений
notnull
Примечание. Другие ограничения сохраняются, например new(), , structclass, allows ref struct, unmanagedи ограничения типов.
Типы группирования расширений
Тип группировки расширений включается в метаданные для каждого набора блоков расширений в исходном коде с одинаковой сигнатурой на уровне CLR.
- Его название невыразимо произнести и определяется на основе содержимого сигнатуры уровня CLR. Дополнительные сведения см. ниже.
- Его параметры типа имеют нормализованные имена (
T0,T1...) и не имеют атрибутов. - Это общедоступный и запечатанный.
- Он помечается флагом
specialnameи атрибутом[Extension].
Имя типа группировки расширений по содержанию основано на сигнатуре CLR и включает в себя следующее:
- Полное имя типа параметра расширения в среде CLR.
- Имена параметров типа будут нормализованы в
T0,T1и так далее, в соответствии с порядком их появления в объявлении типа. - Полное имя не будет включать сборку, содержащую. Обычно типы перемещаются между сборками и не должны прерывать ссылки на xml-документы.
- Имена параметров типа будут нормализованы в
- Ограничения параметров типа будут включены и отсортированы таким образом, чтобы переупорядочение их в исходном коде не изменило имя. Конкретно:
- Ограничения параметров типа будут перечислены в порядке объявления. Ограничения для параметра типа Nth будут возникать перед параметром типа Nth+1.
- Ограничения типов будут отсортированы по полным именам по порядку.
- Ограничения, не относящиеся к типу, упорядочены детерминированно и обрабатываются таким образом, чтобы избежать неоднозначности или столкновения с ограничениями типов.
- Поскольку это не включает атрибуты, оно намеренно игнорирует особенности C#, такие как имена кортежей, учёт nullable и т. д.
Примечание. Имя гарантированно остается стабильным при повторной компиляции, изменениях порядка и изменениях, относящихся к особенностям языка C# (т. е. не влияющих на сигнатуру на уровне CLR).
Типы маркеров расширения
Тип маркера переобъявляет параметры типа группы, которую он содержит (группа расширения), чтобы получить полное представление о C#-виде блоков расширения.
Тип маркера расширения добавляется в метаданные для каждого набора блоков расширений в источнике с одинаковой подписью уровня C#.
- Его имя невыразимое и определяется на основе содержимого сигнатуры уровня C# блока расширения. Дополнительные сведения см. ниже.
- Он заново объявляет параметры типа для содержащего его группирующего типа, которые заявлены в исходном коде (включая имя и атрибуты).
- Он является общедоступным и статическим.
- Он помечается флагом
specialname.
Имя типа маркера расширения, основанного на содержимом, определяется следующим образом:
- Имена параметров типа будут включены в порядке их появления в объявлении расширения.
- Атрибуты параметров типа будут включены и отсортированы таким образом, чтобы их переупорядочение в исходном коде не изменяло имя.
- Ограничения параметров типа будут включены и отсортированы таким образом, чтобы переупорядочение их в исходном коде не изменило имя.
- Полностью определенное имя C# расширенного типа
- К ним относятся такие элементы, как заметки, допускающие значение NULL, имена кортежей и т. д.
- Полностью квалифицированное имя не будет включать содержащую сборку
- Имя параметра расширения
- Модификаторы параметра расширения (
ref,,ref readonly,scoped...) в детерминированном порядке - Полные аргументы имени и атрибута для любых атрибутов, примененных к параметру расширения в детерминированном порядке.
Примечание. Имя гарантированно остается стабильным во время повторной компиляции и перезапорядочения.
Примечание. Типы маркеров расширения и методы маркера расширения включены в референсные сборки.
Метод маркера расширения
Цель метода маркера состоит в кодировании параметра блока расширения. Так как он является членом типа маркера расширения, он может ссылаться на параметры повторно объявленного типа маркера расширения.
Каждый тип маркера расширения содержит один метод, метод маркера расширения.
- Это статический, негенерический метод, возвращающий пустое значение и вызывающийся как
<Extension>$. - Единственный параметр имеет атрибуты, референсность, тип и имя из параметра расширения.
Если параметр расширения не указывает имя, то имя параметра пусто. - Он помечается флагом
specialname.
Доступность метода маркера будет совпадать с наименее ограничивающей доступностью среди соответствующих объявленных членов расширения; если ни один из них не объявлен, используется private.
Члены расширения
Объявления метода или свойства в блоке расширения в источнике представлены как члены типа группировки расширений в метаданных.
- Подписи исходных методов поддерживаются (включая атрибуты), но их тела заменяются
throw NotImplementedException(). - На них не следует ссылаться в IL.
- Методы, свойства и их аксессоры помечены
[ExtensionMarkerName("...")]с указанием имени типа маркера расширения, соответствующего блоку расширения для этого элемента.
Методы реализации
Тела методов для объявлений методов или свойств в блоке расширения в исходном коде создаются как статические методы реализации в статичном классе, находящемся на верхнем уровне.
- Метод реализации имеет то же имя, что и исходный метод.
- Он имеет параметры типа, производные от блока расширения, добавленного к параметрам типа исходного метода (включая атрибуты).
- Он имеет те же специальные возможности и атрибуты, что и исходный метод.
- Если он реализует статический метод, он имеет те же параметры и тип возвращаемого значения.
- Он, если реализует метод экземпляра, он имеет предустановленный параметр к сигнатуре исходного метода. Атрибуты этого параметра, ссылочность, тип и имя выведены из параметра расширения, объявленного в соответствующем блоке расширения.
- Параметры в методах реализации относятся к параметрам типа, принадлежащим методу реализации, а не к параметрам блока расширения.
- Если исходный член является обычным методом экземпляра, метод реализации помечается атрибутом
[Extension].
Атрибут ExtensionMarkerName
Тип ExtensionMarkerNameAttribute предназначен только для использования компилятором— он не разрешен в источнике. Объявление типа синтезируется компилятором, если он еще не включен в компиляцию.
namespace System.Runtime.CompilerServices;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]
public sealed class ExtensionMarkerNameAttribute : Attribute
{
public ExtensionMarkerNameAttribute(string name)
=> Name = name;
public string Name { get; }
}
Примечание: Несмотря на то, что некоторые целевые объекты атрибутов включены для обеспечения будущей совместимости (вложенные типы расширений, поля расширений, события расширений), AttributeTargets.Constructor не включается, поскольку конструкторы расширений не являются стандартными конструкторами.
Пример
Примечание: для удобства чтения мы используем упрощенные названия. Примечание. Поскольку C# не может представлять повторное объявление параметра типа, код, представляющий метаданные, недопустим код C#.
Ниже приведен пример, демонстрирующий работу группирования без элементов:
class E
{
extension<T>(IEnumerable<T> source)
{
... member in extension<T>(IEnumerable<T> source)
}
extension<U>(ref IEnumerable<U?> p)
{
... member in extension<U>(ref IEnumerable<U?> p)
}
extension<T>(IEnumerable<U> source)
where T : IEquatable<U>
{
... member in extension<T>(IEnumerable<U> source) where T : IEquatable<U>
}
}
создается как
[Extension]
class E
{
[Extension, SpecialName]
public sealed class <>E__ContentName_For_IEnumerable_T<T0>
{
[SpecialName]
public static class <>E__ContentName1 // note: re-declares type parameter T0 as T
{
[SpecialName]
public static void <Extension>$(IEnumerable<T> source) { }
}
[SpecialName]
public static class <>E__ContentName2 // note: re-declares type parameter T0 as U
{
[SpecialName]
public static void <Extension>$(ref IEnumerable<U?> p) { }
}
[ExtensionMarkerName("<>E__ContentName1")]
... member in extension<T>(IEnumerable<T> source)
[ExtensionMarkerName("<>E__ContentName2")]
... member in extension<U>(ref IEnumerable<U?> p)
}
[Extension, SpecialName]
public sealed class <>ContentName_For_IEnumerable_T_With_Constraint<T0>
where T0 : IEquatable<T0>
{
[SpecialName]
public static class <>E__ContentName3 // note: re-declares type parameter T0 as U
{
[SpecialName]
public static void <Extension>$(IEnumerable<U> source) { }
}
[ExtensionMarkerName("ContentName3")]
public static bool IsPresent(U value) => throw null!;
}
... implementation methods
}
Ниже приведен пример, на который показано, как создаются элементы:
static class IEnumerableExtensions
{
extension<T>(IEnumerable<T> source) where T : notnull
{
public void Method() { ... }
internal static int Property { get => ...; set => ...; }
public int Property2 { get => ...; set => ...; }
}
extension(IAsyncEnumerable<int> values)
{
public async Task<int> SumAsync() { ... }
}
public static void Method2() { ... }
}
создается как
[Extension]
static class IEnumerableExtensions
{
[Extension, SpecialName]
public sealed class <>E__ContentName_For_IEnumerable_T<T0>
{
// Extension marker type is emitted as a nested type and re-declares its type parameters to include C#-isms
// In this example, the type parameter `T0` is re-declared as `T` with a `notnull` constraint:
// .class <>E__IEnumerableOfT<T>.<>E__ContentName_For_IEnumerable_T_Source
// .typeparam T
// .custom instance void NullableAttribute::.ctor(uint8) = (...)
[SpecialName]
public static class <>E__ContentName_For_IEnumerable_T_Source
{
[SpecialName]
public static <Extension>$(IEnumerable<T> source) => throw null;
}
[ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
public void Method() => throw null;
[ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
internal static int Property
{
[ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
get => throw null;
[ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
set => throw null;
}
[ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
public int Property2
{
[ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
get => throw null;
[ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
set => throw null;
}
}
[Extension, SpecialName]
public sealed class <>E__ContentName_For_IAsyncEnumerable_Int
{
[SpecialName]
public static class <>E__ContentName_For_IAsyncEnumerable_Int_Values
{
[SpecialName]
public static <Extension>$(IAsyncEnumerable<int> values) => throw null;
}
[ExtensionMarkerName("<>E__ContentName_For_IAsyncEnumerable_Int_Values")]
public Task<int> SumAsync() => throw null;
}
// Implementation for Method
[Extension]
public static void Method<T>(IEnumerable<T> source) { ... }
// Implementation for Property
internal static int get_Property<T>() { ... }
internal static void set_Property<T>(int value) { ... }
// Implementation for Property2
public static int get_Property2<T>(IEnumerable<T> source) { ... }
public static void set_Property2<T>(IEnumerable<T> source, int value) { ... }
// Implementation for SumAsync
[Extension]
public static int SumAsync(IAsyncEnumerable<int> values) { ... }
public static void Method2() { ... }
}
Каждый раз, когда члены расширения используются в источнике, мы будем выдавать эти элементы в качестве ссылки на методы реализации.
Например, вызов enumerableOfInt.Method() будет выдаваться как статический вызов IEnumerableExtensions.Method<int>(enumerableOfInt).
XML-документы
Комментарии документации по блоку расширения выводятся для типа маркера (DocID для блока расширения - это E.<>E__MarkerContentName_For_ExtensionOfT'1 в следующем примере).
Они могут ссылаться на параметр расширения и параметры типа, используя <paramref> и <typeparamref> соответственно).
Примечание: вы не можете задокументировать параметр расширения или параметры типа (с <param> и <typeparam>) для элемента расширения.
Если два блока расширения используются как один тип маркера, их документирующие комментарии также объединяются.
Инструменты, обрабатывающие xml-документы, отвечают за копирование <param> и <typeparam> из блока расширения в члены расширения соответствующим образом (т. е. сведения о параметрах должны быть скопированы только для членов экземпляра).
Объект <inheritdoc> создается для методов реализации и непосредственно ссылается на соответствующий член расширения с помощью cref. Например, метод реализации для геттера ссылается на документацию свойства расширения.
Если у члена расширения нет примечаний к документу, то тег <inheritdoc> не включается.
В отношении блоков расширений и членов расширения, мы сейчас не предупреждаем если:
- Параметр расширения задокументирован, но параметры члена расширения не задокументированы.
- или наоборот
- или в эквивалентных сценариях с параметрами незадокументированного типа
Например, следующие комментарии к документации:
/// <summary>Summary for E</summary>
static class E
{
/// <summary>Summary for extension block</summary>
/// <typeparam name="T">Description for T</typeparam>
/// <param name="t">Description for t</param>
extension<T>(T t)
{
/// <summary>Summary for M, which may refer to <paramref name="t"/> and <typeparamref name="T"/></summary>
/// <typeparam name="U">Description for U</typeparam>
/// <param name="u">Description for u</param>
public void M<U>(U u) => throw null!;
/// <summary>Summary for P</summary>
public int P => 0;
}
}
возвращает следующий xml- код:
<?xml version="1.0"?>
<doc>
<assembly>
<name>Test</name>
</assembly>
<members>
<member name="T:E">
<summary>Summary for E</summary>
</member>
<member name="T:E.<>E__MarkerContentName_For_ExtensionOfT`1">
<summary>Summary for extension block</summary>
<typeparam name="T">Description for T</typeparam>
<param name="t">Description for t</param>
</member>
<member name="M:E.<>E__MarkerContentName_For_ExtensionOfT`1.M``1(``0)">
<summary>Summary for M, which may refer to <paramref name="t"/> and <typeparamref name="T"/></summary>
<typeparam name="U">Description for U</typeparam>
<param name="u">Description for u</param>
</member>
<member name="P:E.<>E__MarkerContentName_For_ExtensionOfT`1.P">
<summary>Summary for P</summary>
</member>
<member name="M:E.M``2(``0,``1)">
<inheritdoc cref="M:E.<>E__MarkerContentName_For_ExtensionOfT`1.M``1(``0)"/>
</member>
<member name="M:E.get_P``1(``0)">
<inheritdoc cref="P:E.<>E__MarkerContentName_For_ExtensionOfT`1.P"/>
</member>
</members>
</doc>
Ссылки на CREF
Мы можем рассматривать блоки расширения как вложенные типы, которые можно устранить сигнатурой (как если бы это был метод с одним параметром расширения).
Пример: E.extension(ref int).M().
Но cref не может решить сам блок расширения.
E.extension(int) может ссылаться на метод с именем extension в типе E.
static class E
{
extension(ref int i)
{
void M() { } // can be addressed by cref="E.extension(ref int).M()" or cref="extension(ref int).M()" within E, but not cref="M()"
}
extension(ref int i)
{
void M(int i2) { } // can be addressed by cref="E.extension(ref int).M(int)" or cref="extension(ref int).M(int)" within E
}
}
Процесс поиска знает, что нужно искать во всех соответствующих блоках расширений.
Поскольку мы запрещаем неквалифицированные ссылки на члены расширения, cref также запретит их.
Синтаксис будет следующим:
member_cref
: conversion_operator_member_cref
| extension_member_cref // added
| indexer_member_cref
| name_member_cref
| operator_member_cref
;
extension_member_cref // added
: 'extension' type_argument_list? cref_parameter_list '.' member_cref
;
qualified_cref
: type '.' member_cref
;
cref
: member_cref
| qualified_cref
| type_cref
;
Это ошибка – использовать extension_member_cref на верхнем уровне (extension(int).M) или встраивать его в другое расширение (E.extension(int).extension(string).M).
Кардинальные изменения
Типы и псевдонимы не должны называться "расширение".
Открытые проблемы
Временный раздел документа, связанного с открытыми проблемами, включая обсуждение неопределенного синтаксиса и альтернативных конструкций
- Следует ли изменять требования приемника при доступе к члену расширения? (комментарий)
-
Подтвердите(ответ:extensionvs.extensionsв качестве ключевого словаextension, LDM 2025-03-24) -
Убедитесь, что мы хотим запретить(ответ: да, запретить, LDM 2025-06-11)[ModuleInitializer] -
Подтвердите, что мы можем отбросить блоки расширений как потенциальные точки входа(ответ: да, отбросить, LDM 2025-06-11) -
Подтвердите логику LangVer (пропустите новые расширения и сообщите о них при выборе)(ответ: привязка безусловно и сообщить об ошибке LangVer, за исключением методов расширения экземпляра, LDM 2025-06-11) Следует ли требовать(ответ: примечания документа автоматически объединяются, если блоки объединены, неpartialдля блоков расширений, которые объединяются и объединяют свои комментарии к документации?partialтребуется, подтверждено электронной почтой 2025-09-03)Убедитесь, что элементы не должны называться в честь содержащих или расширенных типов.(ответ: да, подтверждено электронной почтой 2025-09-03)
Пересматривать правила группирования и конфликтов в свете проблемы переносимости: https://github.com/dotnet/roslyn/issues/79043
(ответ. Этот сценарий был разрешен в рамках нового проектирования метаданных с именами типов на основе контента, допускается)
Текущая логика — группировать блоки расширения с одинаковым типом приемника. Это не учитывает ограничения. Это приводит к проблеме совместимости с этим сценарием.
static class E
{
extension<T>(ref T) where T : struct
void M()
extension<T>(T) where T : class
void M()
}
Предлагается использовать ту же логику группирования, которую мы планируем для проектирования типа группировки расширений, а именно учитывать ограничения уровня CLR (то есть игнорировать ограничения notnull, имена кортежей и аннотации нуллабельности).
Должна ли refness кодироваться в названии типа группировки?
-
Просмотрите предложение, которое не должно быть включено в имя типа группировки расширений (требуется дальнейшее обсуждение после того, как(ответ: подтвержден по электронной почте 2025-09-03)refРГ пересмотрет правила группирования и конфликтов, LDM 2025-06-23)
public static class E
{
extension(ref int)
{
public static void M()
}
}
Он испускается следующим образом:
public static class E
{
public static class <>ExtensionTypeXYZ
{
.. marker method ...
void M()
}
}
И сторонние ссылки на CREF создаются как E.extension(ref int).M. Если M:E.<>ExtensionGroupingTypeXYZ.M() удаляется или добавляется к параметру расширения, мы, вероятно, не хотим, чтобы CREF нарушался.
Нас не устраивает этот сценарий, так как любое использование как расширение было бы неоднозначным.
public static class E
{
extension(ref int)
static void M()
extension(int)
static void M()
}
Но мы придаем значение этому сценарию (для переносимости и полезности), и это должно работать с предложенным проектом метаданных после изменения правил конфликтов.
static class E
{
extension<T>(ref T) where T : struct
void M()
extension<T>(T) where T : class
void M()
}
Отсутствие учета ссылок имеет недостаток, так как в этом сценарии мы теряем портативность.
static class E
{
extension<T>(ref T)
void M()
extension<T>(T)
void M()
}
// portability issue: since we're grouping without accounting for refness, the emitted extension members conflict (not implementation members). Mitigation: keep as classic extensions or split to another static class
название
Следует ли запретить свойства расширения в nameof, как мы запрещаем классические и новые методы расширения?(ответ: мы бы хотели использовать `nameof(EnclosingStaticClass.ExtensionMember). Требуется проектирование, скорее всего, punt из .NET 10. LDM 2025-06-11)
Конструкции на основе шаблонов
Методы
Где должны появиться новые методы расширения?(ответ: те же места, где классические методы расширения вступают в игру, LDM 2025-05-05)
Сюда входит следующее:
-
GetEnumerator/GetAsyncEnumeratorвforeach -
Deconstructв деконструкции, в позиционном шаблоне и в цикле foreach -
Addв инициализаторах коллекции -
GetPinnableReferenceвfixed. -
GetAwaiterвawait.
Это исключает:
-
Dispose/DisposeAsyncвusingиforeach -
MoveNext/MoveNextAsyncвforeach -
Sliceиintиндексаторы в имплицитных индексаторах (и, возможно, в шаблонах списка?) -
GetResultвawait.
Свойства и индексаторы
Где должны быть использованы свойства расширения и индексаторы?(ответ: давайте начнем с четырех, LDM 2025-05-05)
Мы бы включили:
- инициализатор объектов:
new C() { ExtensionProperty = ... } - инициализатор словаря:
new C() { [0] = ... } -
with:x with { ExtensionProperty = ... } - Шаблоны свойств:
x is { ExtensionProperty: ... }
Мы бы исключили:
-
Currentвforeach. -
IsCompletedвawait. -
Count/Lengthсвойства и индексаторы в шаблоне списка -
Count/Lengthсвойства и индексаторы в неявных индексаторов
Возвращаемые делегатом свойства
Убедитесь, что свойства расширения этой формы должны вступать в действие только в запросах LINQ, как и свойства экземпляра.(ответ: имеет смысл, LDM 2025-04-06)
Шаблон списка и паттерн распространения
- Убедитесь, что индексаторы расширений
Index/Rangeдолжны воспроизводиться в шаблонах списка (ответ: не относится к C# 14)
Вернитесь к тому, где Count/Length свойства расширения приобретают значение
коллекционные выражения
- Расширение
Addработает - Расширение для распространения
GetEnumeratorработает - Расширение
GetEnumeratorне влияет на определение типа элемента (должен быть экземпляром) - Статические
Createметоды расширения не должны считаться благословленным методом создания - Должны ли счётные свойства расширения влиять на выражения коллекции?
коллекции params
- Расширения
Addне оказывают влияние на типы, которые допускаются сparams
выражения словаря
- Убедитесь, что индексаторы расширений не используются в выражениях словаря, так как наличие индексаторов является неотъемлемой частью, определяющей тип словаря. (ответ: не относится к C# 14)
extern
-
Мы планируем разрешитьexternдля переносимости: (ответ: утвержден, https://github.com/dotnet/roslyn/issues/78572) LDM 2025-06-23
Схема именования и нумерования для типа расширения
Проблема
Текущая система нумерации вызывает проблемы с проверкой общедоступных API, что гарантирует соответствие общедоступных API между ссылочными сборками и сборками реализации.
Следует ли внести одно из следующих изменений? (ответ. Мы внедряем схему именования на основе содержимого для повышения стабильности общедоступного API, и средства по-прежнему должны быть обновлены для учета методов маркеров)
- настройте инструмент
- используйте некоторую схему именования на основе содержимого (TBD)
- разрешить управлять именем с помощью некоторого синтаксиса
Новый универсальный метод Cast по-прежнему не может работать в LINQ
Проблема
В более ранних конструкциях ролей и расширений можно было явно указать только аргументы типа метода.
Но теперь, когда мы сосредоточимся на бессмысленном переходе с классических методов расширения, все аргументы типа должны быть явно заданы.
Это не решает проблему, связанную с использованием метода расширения Cast в LINQ.
Следует ли внести изменения в функцию расширений для удовлетворения этого сценария? (ответ: нет, это не заставляет нас пересмотреть дизайн разрешения расширений, LDM 2025-05-05)
Ограничение параметра расширения для члена расширения
Следует ли разрешить следующее? (ответ: нет, это может быть добавлено позже)
static class E
{
extension<T>(T t)
{
public void M<U>(U u) where T : C<U> { } // error: 'E.extension<T>(T).M<U>(U)' does not define type parameter 'T'
}
}
public class C<T> { }
Нуллабельность
-
Подтвердите текущий дизайн, т. е. максимальную совместимость и портативность(ответ: да, LDM 2025-04-17)
extension([System.Diagnostics.CodeAnalysis.DoesNotReturnIf(false)] bool b)
{
public void AssertTrue() => throw null!;
}
extension([System.Diagnostics.CodeAnalysis.NotNullIfNotNull("o")] ref int? i)
{
public void M(object? o) => throw null!;
}
Метаданные
Должны ли скелетные методы вызывать(ответ: да, LDM 2025-04-17)NotSupportedExceptionили другие стандартные исключения (прямо сейчас мы делаемthrow null;)?Следует ли принимать несколько параметров в методе маркера в метаданных (если новые версии добавляют дополнительные сведения)?(ответ: мы можем оставаться строгим, LDM 2025-04-17)Следует ли пометить маркер расширения или методы реализации, доступные для речи, специальным именем?(ответ: метод маркера должен быть помечен специальным именем, и мы должны проверить его, но не методы реализации, LDM 2025-04-17)Следует ли добавлять(ответ: да, LDM 2025-03-10)[Extension]атрибут в статический класс даже при отсутствии метода расширения экземпляра внутри?Убедитесь, что мы также должны добавить(ответ: нет, LDM 2025-03-10)[Extension]атрибут в методы получения и задания реализации.-
Убедитесь, что типы расширений должны быть помечены специальным именем, и компилятору потребуется этот флаг в метаданных (это критическое изменение из предварительной версии) (ответ: утверждено, LDM 2025-06-23)
Сценарий статической фабрики
Каковы правила конфликтов для статических методов?(ответ: используйте существующие правила C# для заключенного статического типа, без ослабления, LDM 2025-03-17)
Поиск
Как обрабатывать вызовы метода экземпляра теперь, когда у нас есть произносимые имена реализаций?Мы предпочитаем скелетный метод соответствующему методу реализации.Как устранить методы статического расширения?(ответ: как и методы расширения экземпляра, LDM 2025-03-03)Как устранить свойства?(в общих чертах отвечено LDM 2025-03-03, но требуется дальнейшая работа для улучшения)-
Правила области видимости и маскирования для параметров расширения и параметров типа(результат: в пределах области блока расширения, маскирование не допускается, LDM 2025-03-10) Как ORPA следует применять к новым методам расширения?(ответ: рассматривать блоки расширения как прозрачные, "содержащий тип" для ORPA является вложенным статическим классом LDM 2025-04-17)
public static class Extensions
{
extension(Type1)
{
[OverloadResolutionPriority(1)]
public void Overload(...)
}
extension(Type2)
{
public void Overload(...)
}
}
Нужно ли ORPA применяться к новым расширенным свойствам?(ответ: да, ORPA должно быть скопировано в методы реализации LDM 2025-04-23)
public static class Extensions
{
extension(int[] i)
{
public P { get => }
}
extension(ReadOnlySpan<int> r)
{
[OverloadResolutionPriority(1)]
public P { get => }
}
}
- Как повторить правила разрешения классических расширений? Мы?
- обновите стандарт для классических методов расширения и используйте их для описания новых методов расширения.
- сохраняйте существующий язык для классических методов расширения, используйте его для описания новых методов расширения, но имейте плановое отклонение от спецификации для обоих.
- сохраняйте существующий язык для классических методов расширения, но используйте другой язык для новых методов расширения и предусмотреть отклонение спецификации только для классических.
-
Убедитесь, что мы хотим запретить явные аргументы типа для доступа к свойству(ответ: нет доступа к свойствам с явными аргументами типа, рассмотренных в WG)
string s = "ran";
_ = s.P<object>; // error
static class E
{
extension<T>(T t)
{
public int P => 0;
}
}
- Подтвердите, что мы хотим, чтобы
правила улучшения применялись даже если получатель является типом(ответ: параметр расширения только для типа должен учитываться при разрешении статических элементов расширения, LDM 2025-06-23)
int.M();
static class E1
{
extension(int)
{
public static void M() { }
}
}
static class E2
{
extension(in int i)
{
public static void M() => throw null;
}
}
-
Подтвердите, что нас устраивает неоднозначность, когда применяются оба метода и свойства(ответ: мы должны разработать предложение, чтобы сделать лучше, чем статус-кво, исключив из .NET 10, LDM 2025-06-23) -
Удостоверьтесь, что нам не требуется некоторое преимущество для всех членов, прежде чем определить тип победного члена(ответ: исключено из .NET 10, WG 2025-07-02)
string s = null;
s.M(); // error
static class E
{
extension(string s)
{
public System.Action M => throw null;
}
extension(object o)
{
public string M() => throw null;
}
}
У нас есть неявный объект-получатель в объявлениях расширений?(ответ: нет, ранее обсуждалось в LDM)
static class E
{
extension(object o)
{
public void M()
{
M2();
}
public void M2() { }
}
}
Следует ли разрешить поиск по параметру типа?(обсуждение) (ответ: нет, мы будем ждать отзывов, LDM 2025-04-16)
Доступность
Что означает доступность в объявлении расширения?(ответ: объявления расширения не считаются областью специальных возможностей, LDM 2025-03-17)Следует ли применять проверку "несогласованность доступности" для параметра приемника даже для статических членов?(ответ: да, LDM 2025-04-17)
public static class Extensions
{
extension(PrivateType p)
{
// We report inconsistent accessibility error,
// because we generate a `public static void M(PrivateType p)` implementation in enclosing type
public void M() { }
public static void M2() { } // should we also report here, even though not technically necessary?
}
private class PrivateType { }
}
Проверка объявления расширения
Следует ли расслабить проверку параметра типа (выводимость: все параметры типа должны отображаться в типе параметра расширения), где есть только методы?(ответ: да, LDM 2025-04-06) Это позволит портировать 100% классических методов расширения.
Если у вас естьTResult M<TResult, TSource>(this TSource source), вы можете перенести его какextension<TResult, TSource>(TSource source) { TResult M() ... }.Подтвердить, должны ли акцессоры, доступные только для инициализации, быть разрешены в расширениях(ответ: хорошо запретить на данный момент, LDM 2025-04-17)Должно ли быть разрешено(ответ: нет, сохранить указанное правило, LDM 2025-03-24)extension(int receiver) { public void M2() {} }extension(ref int receiver) { public void M2() {} }единственное различие в ref-ness приемника?Должны ли мы жаловаться на конфликт, как это(ответ: да, сохраните указанное правило, LDM 2025-03-24)extension(object receiver) { public int P1 => 1; }extension(object receiver) { public int P1 {set{}} }?Следует ли жаловаться на конфликты между скелетными методами, которые не являются конфликтами между методами реализации?(ответ: да, сохраните указанное правило, LDM 2025-03-24)
static class E
{
extension(object)
{
public void Method() { }
public static void Method() { }
}
}
Текущие правила конфликтов: 1. Проверьте отсутствие конфликтов в аналогичных расширениях с помощью правил класса или структуры, 2. Проверьте отсутствие конфликта между методами реализации в различных объявлениях расширений.
Нам всё ещё нужна первая часть правил?(ответ: да, мы сохраняем эту структуру, так как она помогает с потреблением API, LDM 2025-03-24)
XML-документы
Поддерживается ли(ответ: да, использование paramref для параметра расширения допустимо в членах расширения, LDM 2025-05-05)paramrefпараметр приемника для членов расширения? Даже на статичном? Как кодируется в выходных данных? Вероятно, стандартный способ<paramref name="..."/>будет работать для человека, но существует риск того, что некоторые существующие инструменты могут столкнуться с трудностями, если не найдут его среди параметров API.Должны ли мы копировать комментарии документа в методы реализации с понятными именами?(ответ: нет копирования, LDM 2025-05-05)Должен ли(ответ: нет копирования, LDM 2025-05-05)<param>элемент, соответствующий параметру приемника, копироваться из контейнера расширений для методов экземпляра? Все остальное должно быть скопировано из контейнера в методы реализации (<typeparam>и т. д.) ?Следует ли(ответ: нет, на данный момент, LDM 2025-05-05)<param>разрешить параметр расширения членам расширения в качестве переопределения?- Появится ли сводка по блокам расширений где-либо?
CREF
-
Подтверждение синтаксиса(ответ: предложение хорошо, LDM 2025-06-09) Следует ли ссылаться на блок расширения ((ответ: нет, LDM 2025-06-09)E.extension(int))?Следует ли ссылаться на член с помощью некавалифицированного синтаксиса:(ответ: да, LDM 2025-06-09)extension(int).Member- Следует ли использовать разные символы для непроизносимого имени, чтобы избежать экранирования XML? (ответ: передать на рассмотрение рабочей группе, LDM 2025-06-09)
Убедитесь в том, что обе ссылки на методы скелета и реализации возможны:(ответ: да, LDM 2025-06-09)E.MиE.extension(int).M. Кажется, что они оба необходимы (свойства расширения и переносимость классических методов расширения).Являются ли имена метаданных расширений проблемой для документов версионирования?(ответ: да, мы собираемся уйти от порядковых номеров и использовать схему стабильного именования на основе содержимого)
Добавление поддержки для дополнительных типов элементов
Нам не нужно одновременно реализовать все это проектирование, но может одновременно приблизиться к нему один или несколько типов элементов. На основе известных сценариев в основных библиотеках мы должны работать в следующем порядке:
- Свойства и методы (экземпляр и статические)
- Операторы
- Индексаторы (экземпляры и статические, могут выполняться оппортунистически в более ранней точке)
- Что-нибудь ещё
Сколько мы хотим загрузить внешний дизайн для других видов элементов?
extension_member_declaration // add
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| operator_declaration
| constructor_declaration
| finalizer_declaration
| static_constructor_declaration
| type_declaration
;
Вложенные типы
Если мы решили перейти вперед с вложенными типами расширения, ниже приведены некоторые заметки из предыдущих обсуждений:
- Возникнет конфликт, если два объявления расширения объявят вложенные типы расширений с одинаковыми именами и арностью. У нас нет решения для представления этого в метаданных.
- Грубый подход, который мы обсуждали для метаданных:
- Мы бы выдали скелет вложенный тип с исходными параметрами типа, и никакие члены не были бы
- Мы выпустим вложенный тип реализации с параметрами предустановленного типа из объявления расширения и всех реализаций членов, как они отображаются в источнике (ссылки на модулы на параметры типа)
Конструкторы
Конструкторы обычно описываются как член экземпляра в C#, так как их текст имеет доступ к только что созданному значению через ключевое this слово.
Это не работает хорошо для подхода на основе параметров к членам расширения экземпляра, хотя, так как не существует предварительного значения для передачи в качестве параметра.
Вместо этого конструкторы расширений работают больше, как статические методы фабрики.
Они считаются статическими элементами в том смысле, что они не зависят от имени параметра получателя.
Их тела должны явно создавать и возвращать результат построения.
Сам член по-прежнему объявлен с синтаксисом конструктора, но не может иметь this или base инициализаторы и не зависит от типа приемника с доступными конструкторами.
Это также означает, что конструкторы расширений могут быть объявлены для типов, у которых нет собственных конструкторов, таких как интерфейсы и типы перечисления:
public static class Enumerable
{
extension(IEnumerable<int>)
{
public static IEnumerable(int start, int count) => Range(start, count);
}
public static IEnumerable<int> Range(int start, int count) { ... }
}
Allows:
var range = new IEnumerable<int>(1, 100);
Короткие формы
Предлагаемая конструкция позволяет избежать повторения спецификаций приемника для каждого члена, но в итоге члены расширения оказываются вложенными на два уровня в статический класс и объявление расширения. Скорее всего, для статических классов будет содержаться только одно объявление расширения или объявления расширений, которые содержат только один член, и кажется правдоподобным для нас, чтобы разрешить синтаксическую сокращенность этих случаев.
Объединение статических классов и объявлений расширения:
public static class EmptyExtensions : extension(IEnumerable source)
{
public bool IsEmpty => !source.GetEnumerator().MoveNext();
}
В конечном итоге все больше похоже на то, что мы назвали подходом на основе типов, где контейнер для членов расширения называется сам.
Объединение объявления расширения и элемента расширения:
public static class Bits
{
extension(ref ulong bits) public bool this[int index]
{
get => (bits & Mask(index)) != 0;
set => bits = value ? bits | Mask(index) : bits & ~Mask(index);
}
static ulong Mask(int index) => 1ul << index;
}
public static class Enumerable
{
extension<TSource>(IEnumerable<TSource> source) public IEnumerable<TSource> Where(Func<TSource, bool> predicate) { ... }
}
В конечном итоге все больше похоже на то, что мы назвали подход на основе членов, где каждый член расширения содержит собственную спецификацию приемника.
C# feature specifications