Поделиться через


Аргументы выражения коллекции

Вопрос чемпиона: https://github.com/dotnet/csharplang/issues/8887

Мотивация

Функция выражения словаря определила необходимость передачи данных, указанных пользователем, для настройки поведения конечной коллекции. В частности, словари позволяют пользователям настраивать сравнение ключей, используя их для определения равенства между ключами и сортировки или хэширования (в случае отсортированных или хэшированных коллекций соответственно). Эта необходимость применяется при создании любого типа словаря (например D d = new D(...), D d = D.CreateRange(...) и даже IDictionary<...> d = <synthesized dict>)

Для поддержки этого нового with(...arguments...) элемента предлагается в качестве первого элемента выражения коллекции, как показано ниже:

Dictionary<string, int> nameToAge = [with(comparer), .. d1, .. d2, .. d3];
  1. При переводе в вызов они ...arguments... используются для определения соответствующего new CollectionType(...) конструктора и передаются соответствующим образом.
  2. При преобразовке CollectionFactory.Create в вызов передаются ...arguments... перед аргументом ReadOnlySpan<ElementType> элементов, все из которых используются для определения соответствующей Create перегрузки и передаются соответствующим образом.
  3. При переводе в интерфейс (например IDictionary<,>) допускается только один аргумент. Он реализует один из известных интерфейсов сравнения BCL и будет использоваться для управления ключом сравнения семантики конечного экземпляра.

Этот синтаксис был выбран следующим образом:

  1. Сохраняет все сведения в синтаксисе [...] . Убедитесь, что код по-прежнему четко указывает на созданную коллекцию.
  2. Не подразумевает вызова конструктора new (если это не то, как создаются все коллекции).
  3. Не подразумевает создание и копирование значений коллекции несколько раз (например, постфикс with { ... } .
  4. Не вызывает порядок операций, особенно с семантикой упорядочивания последовательности выражений C#слева направо. Например, он не вычисляет аргументы, используемые для создания коллекции после оценки выражений, используемых для заполнения коллекции.
  5. Не заставляет пользователя читать до конца выражения коллекции (потенциально большого размера), чтобы определить основную семантику поведения. Например, нужно увидеть конец сотнестрого словаря, только чтобы найти это, да, он использовал правильный средство сравнения ключей.
  6. Оба не тонкие, в то время как не слишком подробно. Например, использование ; вместо , указания аргументов является очень простым фрагментом синтаксиса для пропуска. with() добавляет только 6 символов и легко выделяется, особенно с цветом синтаксиса with ключевого слова.
  7. Хорошо читает. "Это выражение коллекции "with" эти аргументы, состоящие из этих элементов".
  8. Решает необходимость сравнения для словарей и наборов.
  9. Гарантирует, что любой пользователь нуждается в передаче аргументов или любые потребности, которые мы сами имеем за пределами сравнения в будущем, уже обрабатываются.
  10. Не конфликтует с существующим кодом (с помощью https://grep.app/ поиска).

Философия проектирования

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

Существует два основных направления, которые можно использовать для предоставления данных, определенных пользователем. Первое — это специальные значения только в пространстве сравнения (которые определяются как типы, наследуемые от BCL IComparer<T> или IEqualityComparer<T> типов). Во-вторых, необходимо предоставить обобщенный механизм для предоставления произвольных аргументов окончательному вызываемому API при создании выражений коллекции. В спецификации основного выражения словаря показано, как можно сделать прежнее, в то время как эта спецификация стремится сделать последнее.

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

  1. Повторное использование синтаксиса элемента, как и в форме: [StringComparer.OrdinalIgnoreCase, "mads": 21] Это хорошо работает в пространстве, где KeyValuePair<,> и сравнения не наследуются от распространенных типов. Но это разбивается в мире, где можно сделать: HashSet<object> h = [StringComparer.OrdinalIgnoreCase, "v"] Это прохождение по сравнениям? Или попытка поместить два значения объекта в набор?

  2. Разделение аргументов и элементов с тонким синтаксисом (например, с запятой вместо запятой для разделения их).[comparer; v1] Это рискует очень запутанными ситуациями, когда пользователь случайно записывает [1; 2] (и получает коллекцию, которая проходит "1", как, например, аргумент "емкость" для a List<>, и содержит только одно значение "2"), когда они предназначены [1, 2] (коллекция с двумя элементами).

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

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

    List<Widget> c = [new(...), w1, w2, w3];
    

    Это является законным сегодня, при new(...) этом выражение является "неявным созданием объекта", которое создает новое мини-приложение. Мы не можем переназначить это для передачи аргументов List<>конструктору, так как это, безусловно, нарушает существующий код.

  2. Этот синтаксис не расширяется за пределами [...] конструкции. Рассмотрим пример.

    HashSet<string> s = [...] with ...;
    

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

  3. Это new как потенциальное ключевое слово, используемое на всех в этом пространстве, является нежелательным запутанным. Оба из-за того, что уже указывается, что создается новый объект, и так как [...] переводы выражения коллекции могут проходить через API-интерфейсы, отличные от конструктора (например, шаблон метода Create).

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

Обратите внимание, что синтаксис, подобный new([...], ...) запуску как "2" и "3" выше. Это означает, что мы вызываем конструктор (когда мы не можем быть) и подразумевает, что созданное выражение коллекции передается этому конструктору, что определенно не так.

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

[with(...arguments...)] Дизайн

Syntax:

collection_element
   : expression_element
   | spread_element
+  | with_element
   ;

+with_element
+  : 'with' argument_list
+  ;

Существует синтаксическая неоднозначность, которая сразу же появилась в этой грамматической рабочей среде. Аналогично неоднозначности между и expression_element (объяснил здесь, существует немедленная синтаксическая неоднозначность между with_elementspread_element и expression_element. with(<arguments>) В частности, именно производственный орган with_elementдля , а также доступен через expression_element -> expression -> ... -> invocation_expression. Существует простое правило иерархии для collection_elements. В частности, если элемент лексически начинается с последовательности with( маркеров, он всегда рассматривается как .with_element

Это полезно двумя способами. Во-первых, реализация компилятора должна рассматриваться только сразу после маркеров, которые он видит, чтобы определить, какой тип элемента необходимо проанализировать. Во-вторых, пользователь может тривиально понять, какой элемент у них есть, не пытаясь проанализировать то, что следует, чтобы увидеть, следует ли думать об этом как о with_element или.expression_element

Примеры

Примеры того, как это будет выглядеть:

// With an existing type:

// Initialize to twice the capacity since we'll have to add
// more values later.
List<string> names = [with(capacity: values.Count * 2), .. values];

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

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

Это было бы критическим изменением, как with(...)могло бы быть вызовом предварительно существующего метода with. Однако, в отличие от new(...)известного и рекомендуемого способа создания неявно типизированных значений, with(...) гораздо менее вероятно, как имя метода, выполняющееся сходства с именованием .Net для методов. В маловероятном случае, когда у пользователя был такой метод, он, безусловно, сможет продолжить вызов существующего метода с помощью @with(...).

Мы преобразуем этот with(...) элемент следующим образом:

List<string> names = [with(/*capacity*/10), ...]; // translates to:

// argument_list *becomes* the argument list for the
// constructor call. 
__result = new List<string>(10); // followed by normal initialization

// or

IList<string> names2 = [with(capacity: 20), ...]; // translates to:

__result = new List<string>(20);

Другими словами, аргументы argument_list будут переданы соответствующему конструктору, если мы вызываем конструктор или в соответствующий метод create, если мы вызываем такой метод. Кроме того, мы позволили бы предоставить один аргумент, наследуемый от типов сравнения BCL, при создании экземпляра одного из типов интерфейса конечного словаря для управления его поведением.

Conversions

Раздел преобразования для коллекций выражений обновляется следующим образом:

> A struct or class type that implements System.Collections.IEnumerable where:

-  * The type has an applicable constructor that can be invoked with no arguments, and the constructor is accessible at the location of the collection expression.
+  a. the collection expression has no `with_element` and the type has an applicable constructor
+     that can be invoked with no arguments, accessible at the location of the collection expression. or
+  b. the collection expression has a `with_element` and the type has at least one constructor
+     accessible at the location of the collection expression. 

Обратите внимание, что фактические аргументы в пределах argument_list не with_element влияют, если преобразование существует или нет. Просто присутствие или отсутствие with_element самого себя. Интуиция здесь просто заключается в том, что если выражение коллекции написано без одного (например [x, y, z]), то придется иметь возможность вызывать конструктор без параметров. Хотя если он есть [with(...), x, y, z] , он может вызвать соответствующий конструктор. Это также означает, что типы, которые не могут вызываться с конструктором без аргументов, можно использовать с выражением коллекции, но только в том случае, если это выражение коллекции, содержащее объект with_element.

Фактическое определение того, как with_element будет влиять на строительство , приведено ниже.

Строительство

Строительство обновляется следующим образом.

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

Если collection_arguments включен и не является первым элементом в выражении коллекции, сообщается об ошибке во время компиляции.

Если список аргументов содержит любые значения с динамическим типом, сообщается ошибка во время компиляции (LDM-2025-01-22).

Конструкторы

Если целевой тип является структурой или типом класса , реализующим System.Collections.IEnumerable, а целевой тип не имеет метода создания, а целевой тип не является универсальным типом параметров .

  • Разрешение перегрузки используется для определения лучшего конструктора экземпляров от кандидатов.
  • Набор конструкторов-кандидатов — это все конструкторы доступных экземпляров, объявленные в целевом типе, применимые к списку аргументов , как определено в соответствующем элементе функции.
  • Если найден лучший конструктор экземпляра, конструктор вызывается со списком аргументов.
    • Если конструктор имеет params параметр, вызов может находиться в развернутой форме.
  • В противном случае сообщается об ошибке привязки.
// List<T> candidates:
//   List<T>()
//   List<T>(IEnumerable<T> collection)
//   List<T>(int capacity)
List<int> l;
l = [with(capacity: 3), 1, 2]; // new List<int>(capacity: 3)
l = [with([1, 2]), 3];         // new List<int>(IEnumerable<int> collection)
l = [with(default)];           // error: ambiguous constructor

Методы CollectionBuilderAttribute

Если целевой тип является типом с методом создания, то:

  • Разрешение перегрузки используется для определения оптимального метода создания от кандидатов.
  • Для каждого метода создания целевого типа мы определяем метод проекции с идентичной сигнатурой метода создания, но без последнего параметра.
  • Набор методов проекции кандидата — это методы проекции , применимые к списку аргументов , как определено в соответствующем элементе функции.
  • Если найден лучший метод проекции, соответствующий метод создания вызывается со списком аргументов , добавленным с ReadOnlySpan<T> элементами.
  • В противном случае сообщается об ошибке привязки.
[CollectionBuilder(typeof(MyBuilder), "Create")]
class MyCollection<T> { ... }

class MyBuilder
{
    public static MyCollection<T> Create<T>(ReadOnlySpan<T> elements);
    public static MyCollection<T> Create<T>(IEqualityComparer<T> comparer, ReadOnlySpan<T> elements);
}
MyCollection<string> c1 = [with(GetComparer()), "1", "2"];
// IEqualityComparer<string> _tmp1 = GetComparer();
// ReadOnlySpan<string> _tmp2 = ["1", "2"];
// c1 = MyBuilder.Create<string>(_tmp1, _tmp2);

MyCollection<string> c2 = [with(), "1", "2"];
// ReadOnlySpan<string> _tmp3 = ["1", "2"];
// c2 = MyBuilder.Create<string>(_tmp3);

CollectionBuilderAttribute: создание методов

Для выражения коллекции, в котором определение целевого типа имеет [CollectionBuilder] атрибут, методы создания являются следующими, обновленными из выражений коллекции: создание методов.

Атрибут [CollectionBuilder(...)] указывает тип построителя и имя метода , вызываемого для создания экземпляра типа коллекции.

Тип построителя должен быть не универсальным class илиstruct.

Сначала определяется набор применимых методовCM создания. Он состоит из методов, которые соответствуют следующим требованиям:

  • Метод должен иметь имя, указанное в атрибуте [CollectionBuilder(...)] .
  • Метод должен быть определен непосредственно в типе построителя .
  • Метод должен быть static.
  • Метод должен быть доступен, где используется выражение коллекции.
  • Arity метода должен соответствовать arity типа коллекции.
  • Метод должен иметь последний параметр типа System.ReadOnlySpan<E>, передаваемый по значению.
  • Существует преобразование удостоверений, неявное преобразование ссылок или преобразование бокса из возвращаемого типа метода в тип коллекции.

Методы, объявленные в базовых типах или интерфейсах, игнорируются и не являются частью CM набора.

Для выражения коллекции с целевым типом C<S0, S1, …> , в котором объявлениеC<T0, T1, …> типа имеет связанный методB.M<U0, U1, …>() построителя, аргументы универсального типа из целевого типа применяются по порядку — и от самого внешнего содержащего типа к самому внутреннему — к методу построителя.

Основные отличия от более ранних алгоритмов:

  • Перед параметромReadOnlySpan<E> могут быть установлены дополнительные параметры.
  • Поддерживаются несколько методов создания.

Тип целевого объекта интерфейса

Если целевой тип является типом интерфейса, то:

  • Разрешение перегрузки используется для определения лучшей подписи метода кандидата.

  • Набор подписей кандидатов — это подписи ниже целевого интерфейса, применимые к списку аргументов , как определено в соответствующем элементе функции.

    Interfaces Подписи кандидатов
    IEnumerable<E>
    IReadOnlyCollection<E>
    IReadOnlyList<E>
    () (нет параметров)
    ICollection<E>
    IList<E>
    List<E>()
    List<E>(int)

Если найдена лучшая сигнатура метода, семантика выглядит следующим образом:

  • Сигнатура кандидата для IEnumerable<E>, IReadOnlyCollection<E> и это просто () и IReadOnlyList<E> имеет то же значение, что и отсутствие with() элемента вообще.
  • Подписи кандидатов IList<T> и ICollection<T> являются подписями List<T>() конструкторов и List<T>(int) подписей. При создании значения (см. перевод интерфейса с мутируемым интерфейсом), вызывается соответствующий List<T> конструктор.
  • В противном случае сообщается об ошибке привязки.

тип целевого объекта Dictionary-Interface

Это указано здесь как часть функции, определенной в https://github.com/dotnet/csharplang/blob/main/proposals/dictionary-expressions.md.

Приведенный выше список дополнен следующими элементами:

Interfaces Подписи кандидатов
IReadOnlyDictionary<K, V> () (нет параметров)
(IEqualityComparer<K>? comparer)
IDictionary<K, V> Dictionary<K, V>()
Dictionary<K, V>(int)
Dictionary<K, V>(IEqualityComparer<K>)
Dictionary<K, V>(int, IEqualityComparer<K>)

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

  • Для подписей IReadOnlyDictionary<K, V>() кандидата являются (которые имеют то же значение, что и элемент не имеет with() вообще), и (IEqualityComparer<K>). Этот средство сравнения будет использоваться для соответствующего хэша и сравнения ключей в целевом словаре компилятор выбирает для создания (см. неизменяемый перевод интерфейса).
  • Подписи кандидатов Dictionary<K, V>()— это подписи IDictionary<T> , Dictionary<K, V>(int)Dictionary<K, V>(IEqualityComparer<K>) а Dictionary<K, V>(int, IEqualityComparer<K>)также конструкторы. При создании значения (см. перевод интерфейса с мутируемым интерфейсом), вызывается соответствующий Dictionary<K, V> конструктор.
  • В противном случае сообщается об ошибке привязки.
IDictionary<string, int> d;
IReadOnlyDictionary<string, int> r;

d = [with(StringComparer.Ordinal)]; // new Dictionary<string, int>(StringComparer.Ordinal)
r = [with(StringComparer.Ordinal)]; // new $PrivateImpl<string, int>(StringComparer.Ordinal)

d = [with(capacity: 2)]; // new Dictionary<string, int>(capacity: 2)
r = [with(capacity: 2)]; // error: 'capacity' parameter not recognized
d = [with()];            // Legal: empty arguments supported for interfaces

Другие целевые типы

Если целевой тип является любым другим типом, для списка аргументов сообщается ошибка привязки, даже если пуста.

Span<int> a = [with(), 1, 2, 3]; // error: arguments not supported
Span<int> b = [with([1, 2]), 3]; // error: arguments not supported

int[] a = [with(), 1, 2, 3]; // error: arguments not supported
int[] b = [with(length: 1), 3]; // error: arguments not supported

Безопасность ссылок

Мы настраиваем правила безопасности collection-expressions.md#ref-safety для учета with() элемента.

См. также ограничение контекста "Безопасный" в разделе "16.4.15".

Создание методов

Этот раздел применяется к выражениям коллекции, целевой тип которого соответствует ограничениям, определенным в методах CollectionBuilderAttribute.

Безопасный контекст определяется путем изменения предложения из collection-expressions.md#ref-safety (изменения полужирного шрифта):

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

Вызовы конструктора

Этот раздел применяется к выражениям коллекции, целевой тип которого соответствует ограничениям, определенным в конструкторах.

Для выражения коллекции типа структуры ссылок следующей формы:
[with(a₁, a₂, ..., aₙ), e₁, e₂, ..., eₙ]

Безопасный контекст выражения коллекции является самым узким из безопасных контекстов следующих выражений:

  • Выражение new C(a₁, a₂, ..., aₙ)создания объекта, где C находится целевой тип
  • Выражения элементов (сами выражения e₁, e₂, ..., eₙ или значение распространения в случае элемента spread).

Аргументы метода должны соответствовать ограничению, применяемого к выражению коллекции. Ограничение применяется путем обработки выражения коллекции как создания формы new C(a₁, a₂, ..., aₙ) { e₁, e₂, ..., eₙ } на один низкоуровневый struct-improvements.md#rules-for-object-initializers.

  • Элементы выражения обрабатываются как инициализаторы элементов коллекции.
  • Распределенные элементы обрабатываются аналогичным образом, временно предполагая, что C имеет Add(SpreadType spread) метод, где SpreadType тип значения распространения.

Ответы на вопросы

Аргументы dynamic

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

Разрешение: Запрещено. LDM-2025-01-22

with() критическое изменение

Предлагаемый with() элемент является критическим изменением.

object x, y, z = ...;
object[] items = [with(x, y), z]; // C#13: ok; C#14: error args not supported for object[]

object with(object x, object y) { ... }

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

Разрешение: Сохраняйте предыдущее поведение (без критических изменений) при компиляции с более ранней версией языка. LDM-2025-03-17

Должны ли аргументы повлиять на преобразование выражений коллекции?

Должны ли аргументы коллекции и применимые методы повлиять на преобразование выражения коллекции?

Print([with(comparer: null), 1, 2, 3]); // ambiguous or Print<int>(HashSet<int>)?

static void Print<T>(List<T> list) { ... }
static void Print<T>(HashSet<T> set) { ... }

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

Print([with(comparer: StringComparer.Ordinal)]); // Print<string>(HashSet<string>)?

Для справки аналогичные случаи с типом new() целевого объекта приводят к ошибкам.

Print<int>(new(comparer: null));              // error: ambiguous
Print(new(comparer: StringComparer.Ordinal)); // error: type arguments cannot be inferred

Разрешение: Аргументы коллекции должны игнорироваться в преобразованиях и выводе типов. LDM-2025-03-17

Порядок параметров метода построителя коллекций

Для методов построителя коллекций следует использовать параметр диапазона до или после каких-либо параметров для аргументов коллекции?

Элементы сначала позволяют объявлять аргументы как необязательные.

class MySetBuilder
{
    public static MySet<T> Create<T>(ReadOnlySpan<T> items, IEqualityComparer<T> comparer = null) { ... }
}

Аргументы сначала позволяют диапазону быть параметром params , чтобы поддерживать вызов непосредственно в развернутой форме.

var s = MySetBuilder.Create(StringComparer.Ordinal, x, y, z);

class MySetBuilder
{
    public static MySet<T> Create<T>(IEqualityComparer<T> comparer, params ReadOnlySpan<T> items) { ... }
}

Разрешение: Параметр диапазона для элементов должен быть последним параметром. LDM-2025-03-12

Аргументы с более ранней версией языка

Сообщается with() ли об ошибке при компиляции с более ранней версией языка или with привязка к другому символу в области?

Разрешение: Критическое изменение with внутри выражения коллекции при компиляции с более ранними языковыми версиями. LDM-2025-03-17

Целевые типы, в которых требуются аргументы

Следует ли поддерживать преобразования выражений коллекции в целевые типы, где должны быть предоставлены аргументы, так как для всех конструкторов или методов фабрики требуется по крайней мере один аргумент?

Такие типы можно использовать с выражениями коллекции, включающими явные with() аргументы, но типы не могут использоваться для params параметров.

Например, рассмотрим следующий тип, созданный из метода фабрики:

MyCollection<object> c;
c = [];                  // error: no arguments
c = [with(capacity: 1)]; // ok

[CollectionBuilder(typeof(MyBuilder), "Create")]
class MyCollection<T> : IEnumerable<T> { ... }

class MyBuilder
{
    public static MyCollection<T> Create<T>(ReadOnlySpan<T> items, int capacity) { ... }
}

Тот же вопрос применяется, когда конструктор вызывается непосредственно, как в приведенном ниже примере.

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

c = [];                  // error: no arguments
c = [with(capacity: 1)]; // error: no constructor callable with no arguments?

class MyCollection<T> : IEnumerable<T>
{
    public MyCollection(int capacity) { ... }
    public void Add(T t) { ... }
    // ...
}

Разрешение: Поддержка преобразований в целевые типы, в которых все конструкторы или методы фабрики требуют аргументов и требуют with() преобразования. LDM-2025-03-05

__arglist

Должно __arglist поддерживаться в with() элементах?

class MyCollection : IEnumerable
{
    public MyCollection(__arglist) { ... }
    public void Add(object o) { }
}

MyCollection c;
c = [with(__arglist())];    // ok
c = [with(__arglist(x, y)]; // ok

Разрешение: Не поддерживается __arglist в аргументах коллекции, если только не бесплатный. LDM-2025-03-05

Аргументы для типов интерфейсов

Следует ли поддерживать аргументы для целевых типов интерфейса?

ICollection<int> c = [with(capacity: 4)];
IReadOnlyDictionary<string, int> d = [with(comparer: StringComparer.Ordinal), ..values];
Если да, какие подписи методов используются при привязке аргументов?

Для типов мутируемых интерфейсов доступны следующие параметры:

  1. Используйте конструкторы со специальными возможностями из известного типа, необходимого для создания экземпляра: List<T> или Dictionary<K, V>.
  2. Используйте подписи независимо от конкретного типа, например использование new() и для ICollection<T> и new(int capacity)IList<T> (см. раздел "Построение потенциальных подписей" для каждого интерфейса).

Использование доступных конструкторов из известного типа имеет следующие последствия:

  • Имена параметров, paramsнеобязательные, взяты из параметров напрямую.
  • Все доступные конструкторы включены, даже если это не может быть полезно для выражений коллекции, таких как List(IEnumerable<T>) что позволит IList<int> list = [with(1, 2, 3)];.
  • Набор конструкторов может зависеть от версии BCL.

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

Для типов интерфейсов, не изменяемых , параметры аналогичны:

  1. Никакие действия не выполняются. Этот
  2. Используйте сигнатуры независимо от конкретного типа, хотя единственный сценарий может быть new(IEqualityComparer<K> comparer) для IReadOnlyDictionary<K, V> C#14..

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

Recomendation: используйте подписи независимо от определенного типа. И, для C# 14, только поддержка new(IEqualityComparer<K> comparer)IReadOnlyDictionary<K, V> , так как это единственный не изменяемый интерфейс, где мы чувствуем, что крайне важно для удобства использования или семантики, чтобы позволить пользователям предоставлять это. Будущие выпуски C# могут рассмотреть возможность расширения этого набора на основе твердых оправданий.

Разрешение:https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-23.md

Аргументы поддерживаются для типов целевых объектов интерфейса. Для мутируемых и не изменяемых интерфейсов набор аргументов будет курироваться.

Ожидаемый список (который по-прежнему должен быть утвержден LDM) является целевым типом интерфейса

Пустые списки аргументов

Следует ли разрешить пустые списки аргументов для некоторых или всех целевых типов?

Пустое with() значение равно no with(). Он может обеспечить некоторую согласованность с непустыми случаями, но он не добавит никаких новых возможностей.

Значение пустого "with()" может быть более понятным для некоторых целевых типов, чем другие: — для типов, где используются **конструкторы** , вызовите применимый конструктор без аргументов. — Для типов с *'CollectionBuilderAttribute'** вызовите применимый метод фабрики только с элементами. — Для **типов интерфейсов* создайте известный или определяемый реализацией тип без аргументов. — Однако для **массивов** и **spans**, где аргументы коллекции не поддерживаются в противном случае, "with()" может быть запутан.
List<int>           l = [with()]; // ok? new List<int>()
ImmutableArray<int> m = [with()]; // ok? ImmutableArray.Create<int>()

IList<int>       i = [with()]; // ok? new List<int>() or equivalent
IEnumerable<int> e = [with()]; // ok?

int[]     a = [with()]; // ok?
Span<int> s = [with()]; // ok?

Разрешение:https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-05-12.md#empty-argument-lists

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

Открытые вопросы

Завершение открытой озабоченности https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-17.md#conclusion

with(...) является критическим изменением языка с [with(...)]. Перед этой функцией это означает выражение коллекции с одним элементом, которое является результатом вызова withвыражения -invocation-. После этого функция представляет собой коллекцию, которая содержит аргументы, переданные в него.

Нужно ли, чтобы этот разрыв происходил только в том случае, если пользователь выбирает определенную языковую версию (например C#-14/15, ?). Другими словами, если они находятся в более старой langversion, они получают предыдущий логику синтаксического анализа, но в более новой версии они получают более новую логику синтаксического анализа. Или мы всегда хотим, чтобы он был более новой логикой синтаксического анализа, даже на более старой langversion?

У нас есть предыдущее искусство для обеих стратегий. requiredНапример, всегда анализируется новая логика независимо от langversion. record/field В то время как другие меняется логикой синтаксического анализа в зависимости от языковой версии.

Наконец, это перекрывает key:value и влияет Dictionary Expressionsна синтаксис элементов KVP. Мы хотим установить поведение, которое мы хотим для любой версии lang, а также для [with(...)] собственных и вещей, таких как [with(...) : expr] или [expr : with(...)].