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


Несвязанные универсальные типы в nameof

Примечание.

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

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

Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .

Проблема чемпиона: https://github.com/dotnet/csharplang/issues/8662

Сводка

Позволяет использовать несвязанные универсальные типы с nameof, как и в nameof(List<>), чтобы получить строку "List", а не указывать неиспользуемый аргумент универсального типа, чтобы получить ту же строку.

Мотивация

Это небольшая функция, которая удаляет общее разочарование: "Почему я должен выбрать аргумент универсального типа, когда выбор не влияет на оценку выражения?" Это очень странно, чтобы требовать, чтобы что-то было указано в операнде, когда он не оказывает влияния на результат. В частности, typeof не страдает от этого ограничения.

Это также не просто о краткости и простоте. После выбора какого-то аргумента произвольного типа в выражении nameof, например object?, изменение ограничения параметра типа может прерывать использование nameof ненужным образом. Оскорбление усугубляет травму в этой ситуации. Для удовлетворения параметра типа иногда может потребоваться объявление фиктивного класса, чтобы реализовать интерфейс, ограничивающий параметр типа. Теперь есть неиспользуемые метаданные и необычное имя, созданные для добавления аргумента типа в выражение nameof. Этот аргумент типа nameof в конечном итоге игнорирует, хотя и требует его.

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

Простое изменение позволяет достичь этого в языке с минимальной сложностью реализации в компиляторе.

Описание

Имена несвязанных типов становятся доступными для использования с nameof:

  • nameof(A<>) оценивается как "A"
  • nameof(Dictionary<,>) оценивается как "Dictionary"

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

class A<T>
{
    public List<T> B { get; }
}
  • nameof(A<>.B) оценивается как "B"
  • nameof(A<>.B.Count) оценивается как "Count"

Доступ к даже членам параметров универсального типа можно получить в соответствии с тем, как nameof уже работает, если тип не является несвязанным. Так как тип неограничен, нет сведений о типах этих членов кроме того, что предоставляет System.Object или какие-либо дополнительные общие ограничения.

class A<TItem, TCollection> where TCollection : IReadOnlyCollection<TItem>
{
    public TCollection B { get; }
}
  • nameof(A<,>.B) оценивается как "B"
  • nameof(A<,>.B.Count) равен "Count".

Не поддерживается

  1. Поддержка не предусмотрена для включения несвязанного типа в качестве аргумента к другому обобщенному типу, например A<B<>> или A<B<>>.C.D. Несмотря на то, что это может быть логически реализовано, такие выражения не имеют прецедента на языке, и нет достаточной мотивации для его введения:

    • A<B<>> не предоставляет никаких преимуществ по сравнению с A<>, и A<B<>>.C не предоставляет никаких преимуществ по сравнению с A<>.C.

    • Если C возвращает TA<T>, A<B<>>.C.D можно записать напрямую как B<>.D. Если он возвращает другой тип, A<B<>>.C.D не предоставляет никаких преимуществ по сравнению с A<>.C.D.

    • typeof() имеет те же ограничения.

  2. Поддержка не включается для частично несвязанных типов, таких как Dictionary<int,>. Аналогичным образом, такие выражения не имеют прецедента на языке, и нет достаточной мотивации. Эта форма не дает никаких преимуществ по сравнению с Dictionary<,>, а доступ к возвращаемым членам Tможно записывать проще без упаковки в частично связанный тип.

Подробный дизайн

Изменения грамматики

nameof_expression
    : 'nameof' '(' named_entity ')'
    ;
    
named_entity
-   : named_entity_target ('.' identifier type_argument_list?)*
+   : named_entity_target ('.' identifier (type_argument_list | generic_dimension_specifier)?)*
    ;
    
named_entity_target
    : simple_name
    | 'this'
    | 'base'
    | predefined_type 
    | qualified_alias_member
    ;

simple_name
-   : identifier type_argument_list?
+   : identifier (type_argument_list | generic_dimension_specifier)?
    ;

generic_dimension_specifier
    : '<' ','* '>'

Теперь это позволяет считать имена, такие как X<> или X<,> простыми именами, тогда как ранее они поддерживались только как unbound_type_nameв выражении typeof.

Семантические изменения

Это ошибка использования generic_dimension_specifier за пределами выражения typeof или nameof. В любом из этих выражений является ошибкой использование generic_dimension_specifier в type_argument_list, array_type, pointer_type или nullable_type. Другими словами, все незаконные:

// Illegal, not inside `nameof` or `typeof`
var v = SomeType<>.StaticMember;
// All illegal
var v = typeof(List<>[]);
var v = typeof(List<>*);
var v = typeof((List<> a, int b));

Примечание. Приведённые выше правила эффективно служат для того, чтобы generic_dimension_specifier не появлялся в пределах другого типа. Тем не менее, на высшем уровне, где разрешено использование спецификатора, можно свободно комбинировать с обычными type_argument_list. Например, следующие варианты являются допустимыми:

var v = (nameof(X<>.Y<int>));
var v = (nameof(X<int>.Y<>));

Поиск элементов в выражении несвязанного типа в nameof будет выполняться так же, как и для выражения this в объявлении этого типа (за исключением выполнения проверок доступности в месте вызова). Другими словами, поиск на основе List<> в nameof(List<>...) работает так же, как и поиск на основе this внутри типа class List<T>.

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

Изменение не требуется, если синтаксис nameof привязывается к методу с именем «nameof», а не является контекстным ключевым словом nameof. Передача любого выражения типа методу приводит к "CS0119: "..." — это тип, недопустимый в заданном контексте". Это уже охватывает несвязанные выражения универсального типа.