Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
Замечание
Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Этот документ включает предлагаемые изменения спецификации, а также информацию, необходимую во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.
Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия отражены в соответствующих заметках с заседания по дизайну языка (LDM) .
Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .
Вопрос чемпиона: https://github.com/dotnet/csharplang/issues/8652
Сводка
Выражения коллекции представляют новый синтаксис terse, [e1, e2, e3, etc]чтобы создать общие значения коллекции. Встраивание других коллекций в эти значения возможно с помощью распределенного элемента ..e , как показано ниже [e1, ..c2, e2, ..c2].
Можно создать несколько типов, таких как коллекция, без поддержки внешнего BCL. Ниже приведены следующие типы:
-
Типы массивов, такие как
int[]. -
Span<T>иReadOnlySpan<T>. - Типы, поддерживающие инициализаторы коллекций, например
List<T>.
Дополнительная поддержка имеется для типов, таких как коллекция, которые не рассматриваются выше с помощью нового атрибута и шаблона API, которые можно применить непосредственно к самому типу.
Мотивация
Значения, подобные коллекции, очень присутствуют в программировании, алгоритмах и особенно в экосистеме C#/.NET. Почти все программы будут использовать эти значения для хранения данных и отправки или получения данных из других компонентов. В настоящее время почти все программы C# должны использовать множество различных и, к сожалению, подробные подходы для создания экземпляров таких значений. Некоторые подходы также имеют недостатки производительности. Ниже приведены некоторые распространенные примеры.
- Массивы, требующие либо до значений, либо
new Type[]new[]перед значениями{ ... }. - Диапазоны, которые могут использовать
stackallocи другие громоздкие конструкции. - Инициализаторы коллекций, которые требуют синтаксиса, например
new List<T>(без вывода возможно подробногоT) до их значений, и которые могут вызвать несколько перемещений памяти, так как они используют вызовы N.Addбез предоставления начальной емкости. - Неизменяемые коллекции, которые требуют синтаксиса, например
ImmutableArray.Create(...)инициализации значений, и которые могут привести к промежуточным выделениям и копированию данных. Более эффективные строительные формы (напримерImmutableArray.CreateBuilder, неуправляемые) и по-прежнему создают неизменяемый мусор.
- Массивы, требующие либо до значений, либо
Глядя на окружающую экосистему, мы также находим примеры во всем мире создания списка, будучи более удобным и приятным для использования. TypeScript, Dart, Swift, Elm, Python и многое другое выбирают краткий синтаксис для этой цели, с широким использованием и большим эффектом. Курсорные исследования не показали никаких существенных проблем, возникающих в этих экосистемах с наличием этих литерала встроенных.
C# также добавил шаблоны списков в C# 11. Этот шаблон позволяет сопоставить и деконструкция значений, таких как список, с помощью чистого и интуитивно понятного синтаксиса. Однако, в отличие от почти всех других конструкций шаблонов, этот синтаксис сопоставления и деконструкции не имеет соответствующего синтаксиса построения.
Получение оптимальной производительности для создания каждого типа коллекции может быть сложной задачей. Простые решения часто тратят как ЦП, так и память. Наличие литеральной формы позволяет обеспечить максимальную гибкость от реализации компилятора, чтобы оптимизировать литерал, чтобы получить по крайней мере хороший результат, как пользователь мог предоставить, но с простым кодом. Очень часто компилятор сможет сделать лучше, и спецификация направлена на то, чтобы обеспечить реализацию больших объемов свободы с точки зрения стратегии реализации.
Для C#требуется инклюзивное решение. Он должен соответствовать подавляющему большинству casse для клиентов с точки зрения типов коллекций и значений, которые у них уже есть. Он также должен чувствовать себя естественным на языке и зеркально работать в сопоставлении шаблонов.
Это приводит к естественному выводу о том, что синтаксис должен быть похож [e1, e2, e3, e-etc] или [e1, ..c2, e2], который соответствует эквивалентам [p1, p2, p3, p-etc] шаблона и [p1, ..p2, p3].
Подробный дизайн
Добавлены следующие грамматические производства:
primary_no_array_creation_expression
...
+ | collection_expression
;
+ collection_expression
: '[' ']'
| '[' collection_element ( ',' collection_element )* ']'
;
+ collection_element
: expression_element
| spread_element
;
+ expression_element
: expression
;
+ spread_element
: '..' expression
;
Известно, что грамматика collection_element представляет неоднозначность синтаксиса.
.. expr В частности, именно производственный орган spread_elementдля , а также доступен через expression_element -> expression -> ... -> range_expression. Существует простое правило collection_elementsдля иерархии. В частности, если элемент лексически начинается с.., то он всегда рассматривается как .spread_element Например, всегда рассматривается как (так) ..x ? y : z; даже если он может быть юридически проанализирован как выражение (например(..x) ? y : z)... (x ? y : z)spread_element
Это полезно двумя способами. Во-первых, реализация компилятора должна рассматривать только первый маркер, который он видит, чтобы определить, что нужно проанализировать далее (a spread_element или expression_element). Во-вторых, пользователь может тривиально понять, какой элемент у них есть, не пытаясь проанализировать то, что следует, чтобы увидеть, следует ли думать о нем как о распространении или выражении.
Литералы коллекции являются целевыми типами.
Уточнения спецификаций
Для краткости
collection_expressionбудет называться "литеральным" в следующих разделах.expression_elementЭкземпляры обычно называютсяe1иe_nт. д.spread_elementЭкземпляры обычно называются..s1и..s_nт. д.Тип диапазона означает либо
Span<T>.ReadOnlySpan<T>Литералы обычно отображаются как
[e1, ..s1, e2, ..s2, etc]для передачи любого количества элементов в любом порядке. Важно, что эта форма будет использоваться для представления всех случаев, таких как:- Пустые литералы
[] - Литералы без
expression_elementних. - Литералы без
spread_elementних. - Литералы с произвольным упорядочением любого типа элемента.
- Пустые литералы
Тип
..s_n— это тип переменной итерации, определяемой как если быs_nони использовались в качестве итерации выражения в объектеforeach_statement.Переменные, начиная
__nameс, используются для представления результатов вычисленияname, хранящегося в расположении, чтобы он был оценен только один раз. Например__e1, оценкаe1.List<T>,IEnumerable<T>и т. д. относятся к соответствующим типамSystem.Collections.Genericв пространстве имен.Спецификация определяет перевод литерала в существующие конструкции C#. Как и перевод выражений запроса, литерал сам по себе является законным, если перевод приведет к юридическому коду. Цель этого правила заключается в том, чтобы избежать необходимости повторять другие правила языка, подразумеваемого (например, о преобразовании выражений при назначении расположениям хранилища).
Реализация не требуется для перевода литералы точно так же, как указано ниже. Любой перевод является законным, если тот же результат производится и нет заметных различий в производстве результата.
- Например, реализация может переводить литералы, такие как
[1, 2, 3]непосредственно вnew int[] { 1, 2, 3 }выражение, которое сама запекает необработанные данные в сборку, ускользая от необходимости__indexили последовательности инструкций для назначения каждого значения. Важно, что это означает, что любой шаг перевода может вызвать исключение во время выполнения, что состояние программы по-прежнему остается в состоянии, указанном переводом.
- Например, реализация может переводить литералы, такие как
Ссылки на "выделение стека" ссылаются на любую стратегию выделения в стеке, а не кучу. Важно, что она не подразумевает или не требует того, чтобы эта стратегия была через фактический
stackallocмеханизм. Например, использование встроенных массивов также является допустимым и желательным подходом для выполнения выделения стека, где он доступен. Обратите внимание, что в C# 12 встроенные массивы нельзя инициализировать с выражением коллекции. Это остается открытым предложением.Предполагается, что коллекции хорошо ведут себя. Рассмотрим пример.
- Предполагается, что значение
Countколлекции будет производить то же значение, что и количество элементов при перечислении. - Типы, используемые в этом спецификации, определенные в
System.Collections.Genericпространстве имен, считаются побочными эффектами без побочных эффектов. Таким образом, компилятор может оптимизировать сценарии, в которых такие типы могут использоваться в качестве промежуточных значений, но в противном случае не предоставляются. - Предполагается, что вызов определенного применимого
.AddRange(x)элемента в коллекции приведет к тому же окончательному значению, что итерацияxи добавление всех перечисленных значений отдельно в коллекцию..Add - Поведение литерала коллекций с коллекциями, которые не хорошо ведут себя, не определены.
- Предполагается, что значение
Conversions
Преобразование выражения коллекции позволяет преобразовать выражение коллекции в тип.
Неявное преобразование выражения коллекции существует из выражения коллекции в следующие типы:
- Одномерный тип
T[]массива, в котором тип элемента являетсяT -
Тип диапазона:
System.Span<T>System.ReadOnlySpan<T>
В этом случае тип элемента имеет значениеT
-
Тип с соответствующим методом создания, в этом случае тип элемента является типом
GetEnumeratorитерации, определенным из метода экземпляра или перечисленного интерфейса, а не из метода расширения. - Тип структуры или класса , реализующий
System.Collections.IEnumerable:Тип имеетприменимый конструктор, который можно вызвать без аргументов, и конструктор доступен в расположении выражения коллекции.
Если выражение коллекции имеет какие-либо элементы, тип имеет экземпляр или метод
Addрасширения, где:- Метод можно вызвать с одним аргументом значения.
- Если метод является универсальным, аргументы типа можно вывести из коллекции и аргумента.
- Метод доступен в расположении выражения коллекции.
В этом случае тип элемента является типом итерациитипа.
-
Тип интерфейса:
System.Collections.Generic.IEnumerable<T>System.Collections.Generic.IReadOnlyCollection<T>System.Collections.Generic.IReadOnlyList<T>System.Collections.Generic.ICollection<T>System.Collections.Generic.IList<T>
В этом случае тип элемента имеет значениеT
Неявное преобразование существует, если тип имеет типT элемента, где для каждого элементаEᵢ в выражении коллекции:
- Если
Eᵢэто элемент выражения, происходит неявное преобразование изEᵢT. - Если
Eᵢэто элемент..Sᵢspread, происходит неявное преобразование из типаSᵢTитерации в .
Преобразование выражений коллекции из выражения коллекции в многомерный тип массива отсутствует.
Типы, для которых существует неявное преобразование выражений коллекции из выражения коллекции, являются допустимыми целевыми типами для этого выражения коллекции.
Следующие дополнительные неявные преобразования существуют из выражения коллекции:
Для типа
T?, в котором выполняется преобразование выражения коллекции из выражения коллекции в типTзначения. Преобразование — это преобразование выражения коллекции , заTкоторым следует неявное преобразование, допускающего значение NULL , вTT?.Для ссылочного типа
T, в котором существует метод создания , связанный сTэтим, возвращает типUи неявное преобразование ссылок изUT. Преобразование — это преобразование выражения коллекции , заUкоторым следует неявное преобразование ссылок вUT.Тип интерфейса
I, в котором существует метод создания , связанный сIэтим, возвращает типVи неявное преобразование бокса изVI. Преобразование — это преобразование выражения коллекции , заVкоторым следует неявное преобразование бокса вVI.
Создание методов
Метод создания указывается атрибутом [CollectionBuilder(...)] типа коллекции.
Атрибут указывает тип построителя и имя метода , вызываемого для создания экземпляра типа коллекции.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface,
Inherited = false,
AllowMultiple = false)]
public sealed class CollectionBuilderAttribute : System.Attribute
{
public CollectionBuilderAttribute(Type builderType, string methodName);
public Type BuilderType { get; }
public string MethodName { get; }
}
}
Атрибут может применяться к объекту class, или structref structinterface.
Атрибут не наследуется, хотя атрибут может применяться к базе class или к объекту abstract class.
Тип построителя должен быть не универсальным class илиstruct.
Сначала определяется набор применимых методовCM создания.
Он состоит из методов, которые соответствуют следующим требованиям:
- Метод должен иметь имя, указанное в атрибуте
[CollectionBuilder(...)]. - Метод должен быть определен непосредственно в типе построителя .
- Метод должен быть
static. - Метод должен быть доступен, где используется выражение коллекции.
- Arity метода должен соответствовать arity типа коллекции.
- Метод должен иметь один параметр типа
System.ReadOnlySpan<E>, передаваемый по значению. - Существует преобразование удостоверений, неявное преобразование ссылок или преобразование бокса из возвращаемого типа метода в тип коллекции.
Методы, объявленные в базовых типах или интерфейсах, игнорируются и не являются частью CM набора.
CM Если набор пуст, тип коллекции не имеет типа элемента и не имеет метода создания. Ни один из следующих шагов не применяется.
Если только один метод из набора CM имеет преобразование удостоверений из Eтипа элемента типа коллекции, то это метод создания для типа коллекции. В противном случае тип коллекции не имеет метода создания.
Сообщается об ошибке, если [CollectionBuilder] атрибут не ссылается на вызываемый метод с ожидаемой сигнатурой.
Для выражения коллекции с целевым типом C<S0, S1, …> , в котором объявлениеC<T0, T1, …> типа имеет связанный методB.M<U0, U1, …>() построителя, аргументы универсального типа из целевого типа применяются по порядку — и от самого внешнего содержащего типа к самому внутреннему — к методу построителя.
Параметр диапазона для метода создания можно явно пометить scoped или [UnscopedRef]. Если параметр неявно или явно scoped, компилятор может выделить хранилище для диапазона в стеке, а не кучу.
Например, возможный метод создания для ImmutableArray<T>:
[CollectionBuilder(typeof(ImmutableArray), "Create")]
public struct ImmutableArray<T> { ... }
public static class ImmutableArray
{
public static ImmutableArray<T> Create<T>(ReadOnlySpan<T> items) { ... }
}
С помощью приведенного выше ImmutableArray<int> ia = [1, 2, 3]; можно создать следующим образом:
[InlineArray(3)] struct __InlineArray3<T> { private T _element0; }
Span<int> __tmp = new __InlineArray3<int>();
__tmp[0] = 1;
__tmp[1] = 2;
__tmp[2] = 3;
ImmutableArray<int> ia =
ImmutableArray.Create((ReadOnlySpan<int>)__tmp);
Construction
Элементы выражения коллекции вычисляются в порядке слева направо. Каждый элемент вычисляется ровно один раз, и все дополнительные ссылки на элементы ссылаются на результаты этой начальной оценки.
Элемент распространения может быть итерирован до или после вычисления последующих элементов в выражении коллекции.
Необработанное исключение, вызванное любым из методов, используемых во время строительства, будет неуправляемым и будет препятствовать дальнейшим шагам в строительстве.
Length, Countи GetEnumerator предполагается, что побочные эффекты не имеют.
Если целевой тип является структурой или типом класса , реализующим System.Collections.IEnumerable, а целевой тип не имеет метода создания, создание экземпляра коллекции выглядит следующим образом:
Элементы вычисляются по порядку. Некоторые или все элементы могут быть оценены во время приведенных ниже шагов, а не раньше.
Компилятор может определить известную длину выражения коллекции путем вызова числовых свойств ( или эквивалентных свойств из известных интерфейсов или типов) для каждого выражения распределенного элемента.
Конструктор, применимый без аргументов.
Для каждого элемента в порядке:
- Если элемент является элементом выражения, применимый
Addэкземпляр или метод расширения вызывается с выражением элемента в качестве аргумента. (В отличие от поведения инициализатора классической коллекции, оценка элементов иAddвызовы не обязательно пересекаются.) - Если элемент является элементом spread , то используется один из следующих элементов:
-
GetEnumeratorПрименимый экземпляр или метод расширения вызывается в выражении распределенного элемента, и для каждого элемента из перечислителя применимыйAddэкземпляр или метод расширения вызывается в экземпляре коллекции с элементом в качестве аргумента. Если перечислитель реализуетIDisposable, вызоветсяDisposeпосле перечисления независимо от исключений. -
AddRangeПрименимый экземпляр или метод расширения вызывается в экземпляре коллекции с выражением распределенного элемента в качестве аргумента. - Применимый
CopyToэкземпляр или метод расширения вызывается в выражении распределенного элемента с экземпляром коллекции иintиндексом в качестве аргументов.
-
- Если элемент является элементом выражения, применимый
Во время описанных выше действий по созданию применимый
EnsureCapacityэкземпляр или метод расширения может вызываться один или несколько раз в экземпляре коллекции с аргументомintемкости.
Если целевой тип является массивом, диапазоном, типом с методом создания или интерфейсом, построение экземпляра коллекции выглядит следующим образом:
Элементы вычисляются по порядку. Некоторые или все элементы могут быть оценены во время приведенных ниже шагов, а не раньше.
Компилятор может определить известную длину выражения коллекции путем вызова числовых свойств ( или эквивалентных свойств из известных интерфейсов или типов) для каждого выражения распределенного элемента.
Экземпляр инициализации создается следующим образом:
- Если целевой тип является массивом , а выражение коллекции имеет известную длину, массив выделяется ожидаемой длиной.
- Если целевой тип является диапазоном или типом с методом создания, а коллекция имеет известную длину, то создается диапазон с ожидаемой длиной, ссылающийся на непрерывное хранилище.
- В противном случае выделяется промежуточное хранилище.
Для каждого элемента в порядке:
- Если элемент является элементом выражения, индексатор экземпляра инициализации вызывается для добавления вычисляемого выражения в текущем индексе.
- Если элемент является элементом spread , то используется один из следующих элементов:
- Элемент хорошо известного интерфейса или типа вызывается для копирования элементов из выражения распределенного элемента в экземпляр инициализации.
-
GetEnumeratorПрименимый экземпляр или метод расширения вызывается в выражении распределенного элемента и для каждого элемента из перечислителя, индексатор экземпляра инициализации вызывается для добавления элемента в текущий индекс. Если перечислитель реализуетIDisposable, вызоветсяDisposeпосле перечисления независимо от исключений. - Применимый
CopyToэкземпляр или метод расширения вызывается в выражении распределенного элемента с экземпляром инициализации иintиндексом в качестве аргументов.
Если промежуточное хранилище было выделено для коллекции, экземпляр коллекции выделяется с фактической длиной коллекции, а значения из экземпляра инициализации копируются в экземпляр коллекции или если диапазон требуется компилятору, может использовать диапазон фактической длины коллекции из промежуточного хранилища. В противном случае экземпляр инициализации — это экземпляр коллекции.
Если целевой тип имеет метод создания, метод создания вызывается с экземпляром диапазона.
Заметка: Компилятор может отложить добавление элементов в коллекцию или отложить итерацию с помощью распределенных элементов до тех пор, пока не будет оценивать последующие элементы. (Если последующие элементы распространения имеют подсчитываемые свойства, которые позволяют вычислять ожидаемую длину коллекции перед выделением коллекции.) И наоборот, компилятор может с нетерпением добавлять элементы в коллекцию — и с нетерпением итерировать с помощью распределенных элементов — когда нет преимуществ для задержки.
Рассмотрим следующее выражение коллекции:
int[] x = [a, ..b, ..c, d];Если распределенные элементы
bиcявляются подсчитываемыми, компилятор может отложить добавление элементов иabдо тех порc, пока не будет оценено, чтобы разрешить выделение результирующего массива по ожидаемой длине. После этого компилятор может с нетерпением добавлять элементы изc, прежде чем оцениватьd.var __tmp1 = a; var __tmp2 = b; var __tmp3 = c; var __result = new int[2 + __tmp2.Length + __tmp3.Length]; int __index = 0; __result[__index++] = __tmp1; foreach (var __i in __tmp2) __result[__index++] = __i; foreach (var __i in __tmp3) __result[__index++] = __i; __result[__index++] = d; x = __result;
Пустой литерал коллекции
Пустой литерал
[]не имеет типа. Однако, аналогично null-литералу, этот литерал может быть неявно преобразован в любой конструкторный тип коллекции.Например, следующее не является законным, так как не существует целевого типа и нет других преобразований, связанных с:
var v = []; // illegalРаспространение пустого литерала разрешено уточить. Рассмотрим пример.
bool b = ... List<int> l = [x, y, .. b ? [1, 2, 3] : []];Здесь, если
bзначение равно false, не требуется, чтобы любое значение фактически было создано для пустого выражения коллекции, так как оно сразу же будет распространяться на нулевые значения в окончательном литерале.Пустое выражение коллекции может быть однотонным, если используется для создания окончательного значения коллекции, которое, как известно, не является изменяемым. Рассмотрим пример.
// Can be a singleton, like Array.Empty<int>() int[] x = []; // Can be a singleton. Allowed to use Array.Empty<int>(), Enumerable.Empty<int>(), // or any other implementation that can not be mutated. IEnumerable<int> y = []; // Must not be a singleton. Value must be allowed to mutate, and should not mutate // other references elsewhere. List<int> z = [];
Безопасность ссылок
Сведения об ограничении безопасного контекста см. в определениях значений безопасного контекста : объявление-блок, член-функция и вызывающий контекст.
Безопасный контекст выражения коллекции:
Безопасный контекст пустого выражения
[]коллекции — это вызывающий контекст.Если целевой тип является типом
System.ReadOnlySpan<T>диапазона иTявляется одним из примитивных типовbool,sbyte,byteshortushortcharintuintlongulongfloatилиdouble, а выражение коллекции содержит только константные значения, безопасный контекст выражения коллекции является вызывающим контекстом.Если целевой тип является типом
System.Span<T>диапазона илиSystem.ReadOnlySpan<T>безопасным контекстом выражения коллекции является блок объявления.Если целевой тип является типом структуры ref с методом создания, безопасный контекст выражения коллекции является безопасным контекстом вызова метода создания, где выражение коллекции является аргументом диапазона для метода.
В противном случае безопасный контекст выражения коллекции является вызывающим контекстом.
Выражение коллекции с безопасным контекстом блока объявления не может избежать включающей области, и компилятор может хранить коллекцию в стеке, а не кучу.
Чтобы разрешить выражению коллекции для типа структуры ссылок экранировать блок объявления, может потребоваться приведение выражения к другому типу.
static ReadOnlySpan<int> AsSpanConstants()
{
return [1, 2, 3]; // ok: span refers to assembly data section
}
static ReadOnlySpan<T> AsSpan2<T>(T x, T y)
{
return [x, y]; // error: span may refer to stack data
}
static ReadOnlySpan<T> AsSpan3<T>(T x, T y, T z)
{
return (T[])[x, y, z]; // ok: span refers to T[] on heap
}
Определение типа
var a = AsArray([1, 2, 3]); // AsArray<int>(int[])
var b = AsListOfArray([[4, 5], []]); // AsListOfArray<int>(List<int[]>)
static T[] AsArray<T>(T[] arg) => arg;
static List<T[]> AsListOfArray<T>(List<T[]> arg) => arg;
Правила вывода типов обновляются следующим образом.
Существующие правила для первого этапа извлекаются в новый раздел вывода типа ввода , а правило добавляется в вывод типов входных данных и вывод выходного типа для выражений выражений коллекции.
11.6.3.2 Первый этап
Для каждого аргумента
Eᵢметода:
- Вывод типа ввода производится из
EᵢсоответствующеготипаTᵢпараметра.Вывод типа входных данных производится из выражения
Eв типTследующим образом:
- Если
Eвыражение коллекции с элементамиEᵢиTявляется типом с типомTₑэлемента илиTимеет типT0?значения NULL иT0имеет типTₑэлемента, то для каждого из нихEᵢ:
- Если
Eᵢявляется элементом выражения, вывод типа входных данных производится изEᵢ.Tₑ- Если
Eᵢявляется элементом распространения с типомSᵢитерации, то вывод с нижней границы производится изSᵢTₑ.- [существующие правила с первого этапа] ...
Вывод типа вывода типа вывода 11.6.3.7
Вывод типа выходных данных производится из выражения
Eв типTследующим образом:
- Если
Eвыражение коллекции с элементамиEᵢиTявляется типом с типомTₑэлемента илиTимеет типT0?значения NULL иT0имеет типTₑэлемента, то для каждого из нихEᵢ:
- Если
Eᵢэто элемент выражения, вывод выходного типа выполняется изEᵢ.Tₑ- Если
Eᵢэто элемент spread, вывод не производится.Eᵢ- [существующие правила вывода типа вывода] ...
Методы расширения
Никаких изменений в правилах вызова метода расширения .
Вызовы метода расширения 12.8.10.3
Метод
Cᵢ.Mₑрасширения имеет право, если:
- ...
- Неявное удостоверение, ссылка или преобразование бокса существует от экспра к типу первого параметра
Mₑ.
Выражение коллекции не имеет естественного типа, поэтому существующие преобразования из типа неприменимо. В результате выражение коллекции нельзя использовать непосредственно в качестве первого параметра для вызова метода расширения.
static class Extensions
{
public static ImmutableArray<T> AsImmutableArray<T>(this ImmutableArray<T> arg) => arg;
}
var x = [1].AsImmutableArray(); // error: collection expression has no target type
var y = [2].AsImmutableArray<int>(); // error: ...
var z = Extensions.AsImmutableArray([3]); // ok
Разрешение перегрузки
Лучшее преобразование из выражения обновляется, чтобы предпочесть определенные целевые типы в преобразованиях выражений коллекции.
В обновленных правилах:
-
Одно из следующих span_type:
System.Span<T>-
System.ReadOnlySpan<T>.
-
Одно из следующих array_or_array_interface:
- Тип массива
- один из следующих типов интерфейса , реализованных типом массива:
System.Collections.Generic.IEnumerable<T>System.Collections.Generic.IReadOnlyCollection<T>System.Collections.Generic.IReadOnlyList<T>System.Collections.Generic.ICollection<T>System.Collections.Generic.IList<T>
Учитывая неявное преобразование
C₁, представляющее собой преобразование из выраженияEв типT₁, и неявное преобразованиеC₂, представляющее собой преобразование из выраженияEв типT₂,C₁— это более подходящее преобразование, чемC₂, если выполняется одно из следующих условий:
Eявляется выражением коллекции и одним из следующих удержаний:
T₁is , и isSystem.ReadOnlySpan<E₁>T₂, иSystem.Span<E₂>неявное преобразование существует изE₁E₂T₁isSystem.ReadOnlySpan<E₁>orSystem.Span<E₁>, иT₂является array_or_array_interface с типомE₂элемента, и неявное преобразование существует изE₁E₂T₁не является span_type иT₂не является span_type, а неявное преобразование существует изT₁T₂Eне является выражением коллекции и одним из следующих удержаний:
Eточно совпадаетT₁иEне соответствует точноT₂Eточно соответствует обоим или ни другимT₁T₂, и являетсяT₁лучшим целевым объектом преобразования , чемT₂E— это группа методов, ...
Примеры различий с разрешением перегрузки между инициализаторами массивов и выражениями коллекции:
static void Generic<T>(Span<T> value) { }
static void Generic<T>(T[] value) { }
static void SpanDerived(Span<string> value) { }
static void SpanDerived(object[] value) { }
static void ArrayDerived(Span<object> value) { }
static void ArrayDerived(string[] value) { }
// Array initializers
Generic(new[] { "" }); // string[]
SpanDerived(new[] { "" }); // ambiguous
ArrayDerived(new[] { "" }); // string[]
// Collection expressions
Generic([""]); // Span<string>
SpanDerived([""]); // Span<string>
ArrayDerived([""]); // ambiguous
Типы диапазонов
Типы диапазонов ReadOnlySpan<T> и Span<T> оба типа являются конструкторными типами коллекций. Поддержка для них соответствует проектированию params Span<T>. В частности, создание любого из этих диапазонов приведет к созданию массива T[], созданного в стеке , если массив params находится в пределах (если таковой) задан компилятором. В противном случае массив будет выделен в куче.
Если компилятор выбирает выделение в стеке, он не требуется переводить литерал непосредственно в определенный stackalloc момент. Например, задано:
foreach (var x in y)
{
Span<int> span = [a, b, c];
// do things with span
}
Компилятор может перевести это значение stackalloc до тех пор, пока Span значение остается неизменным и сохраняется безопасность диапазона . Например, он может перевести приведенный выше текст в следующее:
Span<int> __buffer = stackalloc int[3];
foreach (var x in y)
{
__buffer[0] = a
__buffer[1] = b
__buffer[2] = c;
Span<int> span = __buffer;
// do things with span
}
Компилятор также может использовать встроенные массивы, если они доступны, при выборе выделения в стеке. Обратите внимание, что в C# 12 встроенные массивы нельзя инициализировать с выражением коллекции. Эта функция является открытым предложением.
Если компилятор решит выделить кучу, перевод просто Span<T> :
T[] __array = [...]; // using existing rules
Span<T> __result = __array;
Перевод литерала коллекции
Выражение коллекции имеет известную длину , если тип времени компиляции каждого распределенного элемента в выражении коллекции подсчитывается.
Перевод интерфейса
Преобразование интерфейса, не изменяемого
Учитывая целевой тип, который не содержит мутирующие элементы, а именно IEnumerable<T>IReadOnlyCollection<T>, и IReadOnlyList<T>, соответствующая реализация требуется для создания значения, реализующего этот интерфейс. Если синтезируется тип, рекомендуется использовать синтезированный тип, реализующий все эти интерфейсы, а также ICollection<T> и IList<T>независимо от типа интерфейса. Это обеспечивает максимальную совместимость с существующими библиотеками, в том числе интерфейсы, реализованные значением, чтобы повысить эффективность оптимизации производительности.
Кроме того, значение должно реализовывать негенерические ICollection и IList интерфейсы. Это позволяет выражениям коллекции поддерживать динамическое интроспекция в таких сценариях, как привязка данных.
Совместимая реализация является бесплатной для:
- Используйте существующий тип, реализующий необходимые интерфейсы.
- Синтезирует тип, реализующий необходимые интерфейсы.
В любом случае тип, используемый тип, разрешено реализовать более большой набор интерфейсов, чем те, которые строго требуются.
Синтезированные типы могут использовать любую стратегию, которую они хотят правильно реализовать необходимые интерфейсы. Например, синтезированный тип может встраиваться непосредственно в себя, избегая необходимости дополнительного выделения внутренней коллекции. Синтезированный тип также не может использовать любое хранилище, выбирая вычисление значений напрямую. Например, возврат index + 1 для [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].
- Значение должно возвращаться
trueпри запросеICollection<T>.IsReadOnly(при реализации) и негенерическомIList.IsReadOnlyиIList.IsFixedSize. Это гарантирует, что потребители могут соответствующим образом сообщить, что коллекция не изменяется, несмотря на реализацию изменяемых представлений. - Значение должно вызывать любой вызов метода мутации (например
IList<T>.Add). Это гарантирует безопасность, предотвращая случайное изменение неизменяемой коллекции.
Перевод изменяемого интерфейса
Заданный целевой тип, содержащий мутирующие элементы, а именно ICollection<T> или IList<T>:
- Значение должно быть экземпляром
List<T>.
Известный перевод длины
Наличие известной длины позволяет эффективно создать результат с потенциалом для отсутствия копирования данных и не требуется свободного места в результате.
Отсутствие известной длины не препятствует созданию какого-либо результата. Однако это может привести к дополнительным затратам на ЦП и память, создающим данные, а затем перейти к окончательному назначению.
Для известного литерала
[e1, ..s1, etc]длины перевод сначала начинается со следующего:int __len = count_of_expression_elements + __s1.Count; ... __s_n.Count;Учитывая целевой тип
Tдля этого литерала:Если
Tесть некоторыеT1[], то литерал преобразуется как:T1[] __result = new T1[__len]; int __index = 0; __result[__index++] = __e1; foreach (T1 __t in __s1) __result[__index++] = __t; // further assignments of the remaining elementsРеализация может использовать другие средства для заполнения массива. Например, использование эффективных методов массового копирования, таких как
.CopyTo().Если
Tесть некоторыеSpan<T1>, то литерал преобразуется так же, как и выше, за исключением того, что__resultинициализация преобразуется следующим образом:Span<T1> __result = new T1[__len]; // same assignments as the array translationПеревод может использоваться
stackalloc T1[]или встроенный массив , а неnew T1[]при сохранении безопасности диапазона .Если
Tесть некоторыеReadOnlySpan<T1>, то литерал преобразуется так же, как и вSpan<T1>случае, за исключением того, что окончательный результат будет таким, чтоSpan<T1>неявно преобразован в .ReadOnlySpan<T1>ReadOnlySpan<T1>ГдеT1есть некоторый примитивный тип, и все элементы коллекции являются константой, не требуется, чтобы данные были в куче или на стеке. Например, реализация может создать этот диапазон непосредственно в качестве ссылки на часть сегмента данных программы.Приведенные выше формы (для массивов и диапазонов) представляют собой базовые представления выражения коллекции и используются для следующих правил перевода:
Если
TимеетсяC<S0, S1, …>соответствующий метод create-methodB.M<U0, U1, …>(), то литерал преобразуется как:// Collection literal is passed as is as the single B.M<...>(...) argument C<S0, S1, …> __result = B.M<S0, S1, …>([...])Так как метод создания должен иметь тип аргумента определенного экземпляра
ReadOnlySpan<T>, правило перевода для диапазонов применяется при передаче выражения коллекции в метод создания.Если
Tподдерживает инициализаторы коллекций, выполните следующие действия.Если тип
Tсодержит конструктор со специальными возможностями с одним параметромint capacity, то литерал преобразуется как:T __result = new T(capacity: __len); __result.Add(__e1); foreach (var __t in __s1) __result.Add(__t); // further additions of the remaining elementsПримечание. Имя параметра требуется
capacity.Эта форма позволяет литералу информировать только что созданный тип количества элементов, чтобы обеспечить эффективное выделение внутреннего хранилища. Это позволяет избежать расточительного перемещения по мере добавления элементов.
в противном случае литерал преобразуется как:
T __result = new T(); __result.Add(__e1); foreach (var __t in __s1) __result.Add(__t); // further additions of the remaining elementsЭто позволяет создавать целевой тип, хотя и без оптимизации емкости, чтобы предотвратить внутреннее перемещение хранилища.
Неизвестный перевод длины
Учитывая целевой тип
Tдля неизвестного литерала длины:Если
Tподдерживает инициализаторы коллекций, то литерал преобразуется как:T __result = new T(); __result.Add(__e1); foreach (var __t in __s1) __result.Add(__t); // further additions of the remaining elementsЭто позволяет распространять любой итерируемый тип, хотя и с минимальным количеством оптимизации.
Если
Tесть некоторыеT1[], то литерал имеет ту же семантику, что и:List<T1> __list = [...]; /* initialized using predefined rules */ T1[] __result = __list.ToArray();Приведенный выше неэффективный, хотя; он создает промежуточный список, а затем создает копию окончательного массива из него. Реализации могут быть свободны для оптимизации этого типа, например для создания кода, как показано ниже.
T1[] __result = <private_details>.CreateArray<T1>( count_of_expression_elements); int __index = 0; <private_details>.Add(ref __result, __index++, __e1); foreach (var __t in __s1) <private_details>.Add(ref __result, __index++, __t); // further additions of the remaining elements <private_details>.Resize(ref __result, __index);Это позволяет использовать минимальные затраты и копирование без дополнительных накладных расходов, которые могут возникнуть в коллекциях библиотек.
Количество передаваемых
CreateArrayданных используется для указания начального размера, чтобы предотвратить расточительное изменение размера.Если
Tэто какой-либо тип диапазона, реализация может следовать приведенной вышеT[]стратегии или любой другой стратегии с той же семантикой, но более высокую производительность. Например, вместо выделения массива в качестве копии элементовCollectionsMarshal.AsSpan(__list)списка можно использовать для получения значения диапазона напрямую.
Неподдерживаемые сценарии
Хотя литералы коллекции можно использовать для многих сценариев, существует несколько вариантов, которые они не способны заменить. К ним относятся:
- Многомерные массивы (например,
new int[5, 10] { ... }). Нет возможности включать измерения, и все литералы коллекции являются либо линейными, либо структурами карты. - Коллекции, которые передают специальные значения конструкторам. Доступ к используемому конструктору отсутствует.
- Инициализаторы вложенных коллекций, например
new Widget { Children = { w1, w2, w3 } }. Эта форма должна оставаться, так как она имеет очень разные семантики отChildren = [w1, w2, w3]. Предыдущие вызовы.Addповторяются,.Childrenв то время как последний назначит новую коллекцию.Children. Мы могли бы рассмотреть возможность возврата последней формы к добавлению в существующую коллекцию, если.Childrenее нельзя назначить, но это может быть очень запутанно.
Неоднозначность синтаксиса
Существует две "истинные" синтаксические неоднозначности, в которых существует несколько юридических синтактических интерпретаций кода, использующего код
collection_literal_expression.Неоднозначный
spread_elementс .range_expressionМожно технически иметь следующее:Range[] ranges = [range1, ..e, range2];Чтобы устранить эту проблему, мы можем:
- Требовать от пользователей круглые
(..e)скобки или включения начального индекса0..e, если они хотят диапазон. - Выберите другой синтаксис (например
..., для распространения). Это было бы неудачно в связи с отсутствием согласованности с шаблонами среза.
- Требовать от пользователей круглые
Существует два случая, когда не существует истинной неоднозначности, но где синтаксис значительно увеличивает сложность синтаксического анализа. Хотя и не проблема с учетом времени разработки, это по-прежнему увеличивает когнитивные издержки для пользователей при просмотре кода.
Неоднозначность между
collection_literal_expressionattributesоператорами или локальными функциями. Рассматривать:[X(), Y, Z()]Это может быть одно из следующих вариантов:
// A list literal inside some expression statement [X(), Y, Z()].ForEach(() => ...); // The attributes for a statement or local function [X(), Y, Z()] void LocalFunc() { }Без сложного внешнего вида, было бы невозможно сказать, не потребляя всю полноту литерала.
Возможные варианты для решения этой проблемы:
- Разрешите это, выполняя синтаксический анализ, чтобы определить, какие из этих случаев это.
- Запретить это и требовать, чтобы пользователь завернуть литерал в круглые скобки, например
([X(), Y, Z()]).ForEach(...). - Неоднозначность между
collection_literal_expressionconditional_expressiona и anull_conditional_operations. Рассматривать:
M(x ? [a, b, c]Это может быть одно из следующих вариантов:
// A ternary conditional picking between two collections M(x ? [a, b, c] : [d, e, f]); // A null conditional safely indexing into 'x': M(x ? [a, b, c]);Без сложного внешнего вида, было бы невозможно сказать, не потребляя всю полноту литерала.
Примечание. Это проблема даже без естественного типа , так как целевая типизация применяется через
conditional_expressions.Как и в других, мы могли бы требовать, чтобы круглые скобки диамбигуат. Другими словами, предположим
null_conditional_operation, что интерпретация не написана так:x ? ([1, 2, 3]) :Тем не менее, это кажется довольно несчастным. Этот вид кода, кажется, не является необоснованным для написания и, скорее всего, поездок людей вверх.
Недостатки
- В этой статье представлена еще одна форма для выражений коллекции поверх уже имеющихся многомерных способов. Это дополнительная сложность для языка. Тем не более чем это делает возможным объединение по одному синтаксису
кольцадля правила всех, что означает, что существующие базы кода могут быть упрощены и перемещены в единый вид везде. - Использование
[... вместо]...{}переходит от синтаксиса, который мы обычно использовали для инициализаторов массивов и коллекций. В частности, что он использует[...]вместо{...}. Однако это уже было решено командой языка, когда мы сделали шаблоны списка. Мы пытались сделать{...}работать с шаблонами списка и столкнулись с непреодолимыми проблемами. Из-за этого мы переехали[в ...], что, в то время как новое для C#, чувствует себя естественным во многих языках программирования и позволил нам начать свежие без неоднозначности. Использование[...]как соответствующая литеральная форма дополняет наши последние решения, и дает нам чистое место для работы без проблем.
Это вводит бородавки на язык. Например, следующие как юридические, так и (к счастью) означают то же самое:
int[] x = { 1, 2, 3 };
int[] x = [ 1, 2, 3 ];
Тем не менее, учитывая широту и согласованность нового синтаксиса литерала, мы должны рекомендовать, чтобы люди переходили в новую форму. Предложения и исправления интегрированной среды разработки могут помочь в этом отношении.
Alternatives
- Какие другие проекты были рассмотрены? Что такое влияние не делать этого?
Устраненные вопросы
Следует ли компилятору использовать
stackallocвыделение стека, если встроенные массивы недоступны, а тип итерации является примитивным типом?Разрешение: Нет. Управление буфером требует дополнительных усилий
stackallocпо встроенному массиву , чтобы убедиться, что буфер не выделяется многократно, когда выражение коллекции находится в цикле. Дополнительная сложность компилятора и в созданном коде перевешивает преимущество выделения стека на старых платформах.В каком порядке следует оценивать литеральные элементы по сравнению с оценкой свойств Length/Count? Следует ли сначала оценивать все элементы, а затем все длины? Или мы должны оценить элемент, а затем его длину, а затем следующий элемент и т. д.?
Решение: сначала мы вычисляем все элементы, а все остальные следуют этому.
Может ли ли литерал неизвестной длины создать тип коллекции, требующий известной длины, например коллекции массива, диапазона или конструктора (массива или диапазона) ? Это было бы трудно сделать эффективно, но это может быть возможно с помощью умного использования объединенных массивов и /или построителей.
Разрешение. Да, мы разрешаем создавать коллекцию исправлений длины из неизвестного литерала длины. Компилятор может реализовать это как можно эффективнее.
Следующий текст существует для записи первоначального обсуждения этого раздела.
Пользователи всегда могут сделать неизвестный литерал длины в известную длину одной с кодом следующим образом:
ImmutableArray<int> x = [a, ..unknownLength.ToArray(), b];Однако это связано с необходимостью принудительного выделения временного хранилища. Мы могли бы быть более эффективными, если бы мы контролировали, как это было создано.
Можно ли использовать целевой
collection_expressionтипIEnumerable<T>для интерфейсов коллекции или других интерфейсов коллекции?Рассмотрим пример.
void DoWork(IEnumerable<long> values) { ... } // Needs to produce `longs` not `ints` for this to work. DoWork([1, 2, 3]);Разрешение. Да, литерал может быть предназначен для любого типа
I<T>интерфейса, реализующегоList<T>. Например:IEnumerable<long>. Это аналогично вводу целевогоList<long>объекта, а затем назначению этого результата указанному типу интерфейса. Следующий текст существует для записи первоначального обсуждения этого раздела.Открытый вопрос здесь определяет, какой базовый тип фактически создается. Один из вариантов заключается в том, чтобы взглянуть на предложение
params IEnumerable<T>. Там мы создадим массив для передачи значений вместе, как и с тем, сparams T[]чем происходит.Может или должен ли компилятор выпустить
Array.Empty<T>()?[]Следует ли нам заставить это сделать, чтобы избежать выделения по возможности?Да. Компилятор должен выдаваться
Array.Empty<T>()в любом случае, если это является законным, и окончательный результат не изменяется. Например, нацеливаниеT[]илиIEnumerable<T>IReadOnlyCollection<T>IReadOnlyList<T>. Он не должен использоватьArray.Empty<T>, если целевой объект является изменяемым (ICollection<T>илиIList<T>).Следует ли расширить инициализаторы коллекций, чтобы найти очень распространенный
AddRangeметод? Он может использоваться базовым созданным типом для эффективного добавления элементов распространения. Мы также можем искать такие вещи, как.CopyToи. Здесь могут возникнуть недостатки, так как эти методы могут привести к возникновению избыточных выделений или отправки, а также непосредственно перечисления в переведенном коде.Да. Реализация позволяет использовать другие методы для инициализации значения коллекции в соответствии с предположением, что эти методы имеют четко определенную семантику, и типы коллекций должны быть "хорошо вести себя". На практике реализация должна быть осторожной, так как преимущества в одном из способов (массовое копирование) могут привести к негативным последствиям (например, боксирование коллекции структур).
Реализация должна воспользоваться преимуществами в тех случаях, когда нет недостатков. Например, с методом
.AddRange(ReadOnlySpan<T>).
Неразрешенные вопросы
- Следует ли разрешать вывод типа элемента , когда тип итерации является неоднозначным (по некоторым определениям)? Рассмотрим пример.
Collection x = [1L, 2L];
// error CS1640: foreach statement cannot operate on variables of type 'Collection' because it implements multiple instantiations of 'IEnumerable<T>'; try casting to a specific interface instantiation
foreach (var x in new Collection) { }
static class Builder
{
public Collection Create(ReadOnlySpan<long> items) => throw null;
}
[CollectionBuilder(...)]
class Collection : IEnumerable<int>, IEnumerable<string>
{
IEnumerator<int> IEnumerable<int>.GetEnumerator() => throw null;
IEnumerator<string> IEnumerable<string>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
Должен ли он быть законным для создания и немедленного индексирования в литерал коллекции? Примечание. Это требует ответа на неразрешенный вопрос ниже о том, имеют ли литералы коллекции естественный тип.
Выделение стека для огромных коллекций может привести к стеку. Должен ли компилятор иметь эвристические данные для размещения этих данных в куче? Должен ли язык не быть указан, чтобы обеспечить эту гибкость? Мы должны следовать спецификации для
params Span<T>.Нужно ли нам использовать целевой тип
spread_element? Рассмотрим, например:Span<int> span = [a, ..b ? [c] : [d, e], f];Примечание. Обычно это может возникнуть в следующей форме, чтобы разрешить условное включение некоторых элементов или ничего, если условие равно false:
Span<int> span = [a, ..b ? [c, d, e] : [], f];Чтобы оценить этот полный литерал, необходимо оценить выражения элементов внутри. Это означает, что возможность оценить
b ? [c] : [d, e]. Тем не менее, отсутствует целевой тип для оценки этого выражения в контексте и отсутствует какой-либо естественный тип, это не позволит нам определить, что делать с любым[c]или[d, e]здесь.Чтобы устранить эту проблему, можно сказать, что при оценке выражения литерала
spread_elementсуществовал неявный целевой тип, эквивалентный целевому типу самого литерала. Таким образом, в приведенном выше коде будет перезаписан следующим образом:int __e1 = a; Span<int> __s1 = b ? [c] : [d, e]; int __e2 = f; Span<int> __result = stackalloc int[2 + __s1.Length]; int __index = 0; __result[__index++] = a; foreach (int __t in __s1) __result[index++] = __t; __result[__index++] = f; Span<int> span = __result;
Спецификация типа создаваемой коллекции, использующая метод создания , учитывает контекст, по которому преобразование классифицируется.
Существование преобразования в данном случае зависит от понятия типа итерациитипа коллекции. Если существует метод создания , который принимает ReadOnlySpan<T>Tтип итерации, преобразование существует. В противном случае это не так.
Однако тип итерации учитывает контекст, с которым foreach выполняется. Для одного типа коллекции он может отличаться в зависимости от того, какие методы расширения находятся в области, и он также может быть неопределенным.
Это чувствует себя хорошо для целей, foreach когда тип не предназначен для того, чтобы быть на самом деле доступным для себя. Если это так, методы расширения не могут изменить способ перебора типа, независимо от контекста.
Тем не менее, это чувствует себя несколько странно для преобразования, чтобы быть контекст чувствительным к такому. Фактически преобразование является "неустойчивым". Тип коллекции, явно разработанный для создания, позволяет исключить определение очень важных деталей — его тип итерации. Оставляя тип "непобедимым" на самом себе.
Рассмотрим пример:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
[CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))]
class MyCollection
{
}
class MyCollectionBuilder
{
public static MyCollection Create(ReadOnlySpan<long> items) => throw null;
public static MyCollection Create(ReadOnlySpan<string> items) => throw null;
}
namespace Ns1
{
static class Ext
{
public static IEnumerator<long> GetEnumerator(this MyCollection x) => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
long s = l;
}
MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long'
2];
}
}
}
namespace Ns2
{
static class Ext
{
public static IEnumerator<string> GetEnumerator(this MyCollection x) => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
string s = l;
}
MyCollection x1 = ["a",
2]; // error CS0029: Cannot implicitly convert type 'int' to 'string'
}
}
}
namespace Ns3
{
class Program
{
static void Main()
{
// error CS1579: foreach statement cannot operate on variables of type 'MyCollection' because 'MyCollection' does not contain a public instance or extension definition for 'GetEnumerator'
foreach (var l in new MyCollection())
{
}
MyCollection x1 = ["a", 2]; // error CS9188: 'MyCollection' has a CollectionBuilderAttribute but no element type.
}
}
}
Учитывая текущую структуру, если тип не определяет сам тип итерации , компилятор не может надежно проверить приложение атрибута CollectionBuilder . Если мы не знаем тип итерации, мы не знаем, какая подпись метода создания должна быть. Если тип итерации поступает из контекста, нет никакой гарантии, что тип всегда будет использоваться в аналогичном контексте.
Функция коллекций params также влияет на эту функцию. Это чувствует себя странно, чтобы не удалось надежно прогнозировать тип params элемента параметра в точке объявления. Текущее предложение также требует убедиться, что метод создания по крайней мере доступен params как тип коллекции. Невозможно выполнить эту проверку надежным способом, если только тип коллекции не определяет сам тип итерации .
Обратите внимание, что мы также https://github.com/dotnet/roslyn/issues/69676 открыли для компилятора, который в основном наблюдает ту же проблему, но говорит об этом с точки зрения оптимизации.
Предложение
Требовать тип, используюющий CollectionBuilder атрибут, чтобы определить его тип итерации на самом себе.
Другими словами, это означает, что тип должен либо реализовать IEnumarable/IEnumerable<T>, либо он должен иметь открытый GetEnumerator метод с правильной подписью (это исключает любые методы расширения).
Кроме того, теперь для создания метода требуется "быть доступным, где используется выражение коллекции". Это еще одна точка зависимости контекста на основе специальных возможностей. Цель этого метода очень похожа на назначение определяемого пользователем метода преобразования, и что он должен быть общедоступным. Поэтому мы должны рассмотреть вопрос о том, чтобы метод создания был открытым, а также.
Conclusion
Утверждено с изменениями LDM-2024-01-08
Понятие типа итерации не применяется последовательно во время преобразований
- Для структуры или типа класса , реализующего
System.Collections.Generic.IEnumerable<T>:
- Для каждого элемента
Eiимеется неявное преобразованиеTв .
В данном случае предполагается, что T необходимо использовать тип итерацииструктуры или типа класса .
Однако это предположение неверно. Что может привести к очень странному поведению. Рассмотрим пример.
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable<long>
{
IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public void Add(string l) => throw null;
public IEnumerator<string> GetEnumerator() => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
string s = l; // Iteration type is string
}
MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long'
2];
MyCollection x2 = new MyCollection() { "b" };
}
}
- В структуру или тип класса, реализующий и
System.Collections.IEnumerableSystem.Collections.Generic.IEnumerable<T>.
Похоже, реализация предполагает, что тип итерации имеет значение object, но спецификация оставляет этот факт не указан, и просто не требует, чтобы каждый элемент преобразовывалось в что-либо. Однако в целом тип итерации не требуется object . Что можно увидеть в следующем примере:
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable
{
public IEnumerator<string> GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
string s = l; // Iteration type is string
}
}
}
Понятие типа итерации является фундаментальным для функции Params Collections . И этот вопрос приводит к странным расхождениям между двумя признаками. Пример:
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable<long>
{
IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public IEnumerator<string> GetEnumerator() => throw null;
public void Add(long l) => throw null;
public void Add(string l) => throw null;
}
class Program
{
static void Main()
{
Test("2"); // error CS0029: Cannot implicitly convert type 'string' to 'long'
Test(["2"]); // error CS1503: Argument 1: cannot convert from 'collection expressions' to 'string'
Test(3); // error CS1503: Argument 1: cannot convert from 'int' to 'string'
Test([3]); // Ok
MyCollection x1 = ["2"]; // error CS0029: Cannot implicitly convert type 'string' to 'long'
MyCollection x2 = [3];
}
static void Test(params MyCollection a)
{
}
}
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable
{
IEnumerator IEnumerable.GetEnumerator() => throw null;
public IEnumerator<string> GetEnumerator() => throw null;
public void Add(object l) => throw null;
}
class Program
{
static void Main()
{
Test("2", 3); // error CS1503: Argument 2: cannot convert from 'int' to 'string'
Test(["2", 3]); // Ok
}
static void Test(params MyCollection a)
{
}
}
Вероятно, это будет хорошо выровнять один или другой.
Предложение
Укажите преобразование структуры или типа класса , реализующего System.Collections.Generic.IEnumerable<T> или System.Collections.IEnumerable с точки зрения типа итерации , и требует неявного преобразования для каждого элементаEi в тип итерации.
Conclusion
Утвержден LDM-2024-01-08
Требуется ли преобразование выражений коллекции для создания минимального набора API?
Конструкторный тип коллекции в соответствии с преобразованиями может быть неконструируемым, что, скорее всего, приведет к некоторому непредвиденному поведению разрешения перегрузки. Рассмотрим пример.
class C1
{
public static void M1(string x)
{
}
public static void M1(char[] x)
{
}
void Test()
{
M1(['a', 'b']); // error CS0121: The call is ambiguous between the following methods or properties: 'C1.M1(string)' and 'C1.M1(char[])'
}
}
Тем не менее, 'C1. M1(string)' не является кандидатом, который можно использовать, так как:
error CS1729: 'string' does not contain a constructor that takes 0 arguments
error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
Ниже приведен еще один пример с определяемым пользователем типом и более сильной ошибкой, которая даже не упоминает допустимого кандидата:
using System.Collections;
using System.Collections.Generic;
class C1 : IEnumerable<char>
{
public static void M1(C1 x)
{
}
public static void M1(char[] x)
{
}
void Test()
{
M1(['a', 'b']); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?)
}
public static implicit operator char[](C1 x) => throw null;
IEnumerator<char> IEnumerable<char>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
Похоже, что ситуация очень похожа на то, что мы использовали с группой методов для делегирования преобразований. То есть существовали сценарии, в которых существовало преобразование, но было ошибочно. Мы решили улучшить это, убедив, что, если преобразование ошибочно, то оно не существует.
Обратите внимание, что с функцией Params Collections мы будем работать с аналогичной проблемой. Может быть хорошо запретить использование params модификатора для неконструируемых коллекций. Однако в текущем предложении , которое проверяется на основе раздела преобразования. Рассмотрим пример:
using System.Collections;
using System.Collections.Generic;
class C1 : IEnumerable<char>
{
public static void M1(params C1 x) // It is probably better to report an error about an invalid `params` modifier
{
}
public static void M1(params ushort[] x)
{
}
void Test()
{
M1('a', 'b'); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?)
M2('a', 'b'); // Ok
}
public static void M2(params ushort[] x)
{
}
IEnumerator<char> IEnumerable<char>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
Похоже, что вопрос был несколько рассмотрен ранее, см. в статье https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-02.md#collection-expressions. В то время был сделан аргумент, что правила, указанные прямо сейчас, соответствуют тому, как интерполированные обработчики строк указаны. Ниже приведена цитата:
В частности, интерполированные обработчики строк были первоначально указаны таким образом, но мы пересмотрели спецификацию после рассмотрения этой проблемы.
Хотя существует некоторое сходство, существует также важное различие, которое стоит рассмотреть. Вот цитата из https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md#interpolated-string-handler-conversion:
Тип
T, как говорят, является applicable_interpolated_string_handler_type , если он относится кSystem.Runtime.CompilerServices.InterpolatedStringHandlerAttribute. Существует неявная interpolated_string_handler_conversionTиз interpolated_string_expression или additive_expression , полностью состоящая из _interpolated_string_expression_s и использующего только+операторы.
Целевой тип должен иметь специальный атрибут, который является сильным индикатором намерения автора для того, чтобы тип был интерполированным обработчиком строк. Справедливо предположить, что наличие атрибута не является совпадением.
В отличие от этого, тот факт, что тип является "перечисленным", не требуется означать, что у автора было намерение автора для того, чтобы тип был конструируемым. Однако наличие метода создания, указывающего атрибутом [CollectionBuilder(...)] в типе коллекции, чувствует себя сильным индикатором намерения автора для того, чтобы тип был конструируемым.
Предложение
Для типа структуры или класса, реализующего System.Collections.IEnumerable и не имеющих раздела преобразованияметодов создания, должно потребоваться наличие по крайней мере следующих API:
- Конструктор со специальными возможностями, применимый без аргументов.
-
AddДоступный экземпляр или метод расширения, который можно вызвать со значением типа итерации в качестве аргумента.
Для функции Params Collectons такие типы являются допустимыми params типами, когда эти API объявлены общедоступными и являются методами экземпляра (vs. extension).
Conclusion
Утверждено с изменениями LDM-2024-01-10
C# feature specifications