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


Локальные типы файлов

Заметка

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

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

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

Выпуск Чемпион: https://github.com/dotnet/csharplang/issues/5529

Сводка

Разрешить модификатор file в объявлениях типов верхнего уровня. Тип существует только в файле, в котором он объявлен.

// File1.cs
namespace NS;

file class Widget
{
}

// File2.cs
namespace NS;

file class Widget // different symbol than the Widget in File1
{
}

// File3.cs
using NS;

var widget = new Widget(); // error: The type or namespace name 'Widget' could not be found.

Мотивация

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

  1. Эти файлы должны содержать сведения о реализации, скрытые от остальной части компиляции, но доступны для использования в файле, в котором они объявлены.
  2. Мы хотим снизить необходимость генераторам искать имена типов, которые не будут конфликтовать с объявлениями в пользовательском коде или коде других генераторов.

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

  • Мы добавим модификатор file в следующие наборы модификаторов:
  • Модификатор file можно использовать только в типе верхнего уровня.

Если тип имеет модификатор file, он считается типом локального файла.

Доступность

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

public file class C1 { } // error
internal file class C2 { } // error
file class C3 { } // ok

Именование

Реализация гарантирует, что локальные типы файлов в разных файлах с одинаковым именем будут отличаться от среды выполнения. Доступность и имя типа в метаданных определяются реализацией. Цель заключается в том, чтобы компилятор принял все будущие функции ограничения доступа в среде выполнения, которые подходят для этой функции. Ожидается, что в начальной реализации будет использоваться доступность internal, и будет использоваться нечитаемое сгенерированное имя, которое зависит от файла, в котором объявлен тип.

Поиск

Мы изменим раздел подстановки (новый текст в полужирном):

  • Затем, если K равно нулю, все вложенные типы, объявления которых включают параметры типа, удаляются. Если K не равно нулю, удаляются все члены с другим числом параметров типа. Если K равно нулю, методы с параметрами типа не удаляются, так как процесс вывода типов (§11.6.3) может иметь возможность выводить аргументы типа.
  • Затем давайте F быть единицей компиляции, содержащей выражение, в котором происходит поиск элементов. Все члены, которые являются локальными типами файлов и не объявляются в F удаляются из набора.
  • Далее, если набор доступных элементов содержит локальные типы файлов, все члены, которые не являются локальными типами файлов, удаляются из набора.

Замечания

Эти правила запрещают использование локальных типов файлов за пределами файла, в котором они объявлены.

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

// File1.cs
class C
{
    public static void M() { }
}
// File2.cs
file class C
{
    public static void M() { }
}

class Program
{
    static void Main()
    {
        C.M(); // refers to the 'C' in File2.cs
    }
}

Обратите внимание, что мы не обновляем раздел спецификации областей . Это связано с тем, как указано в спецификации:

Область имени — это область текста программы, в которой можно ссылаться на сущность, объявленную именем без квалификации имени.

В действительности область видимости влияет только на процедуру поиска неквалифицированных имен. Это не совсем подходящая для нас концепция, поскольку нам также нужно влиять на процесс поиска квалифицированных имен.

// File1.cs
namespace NS1
{
    file class C
    {
        public static void M() { }
    }
}

namespace NS2
{
    class Program
    {
        public static void M()
        {
            C.M(); // error: C is not in scope
            NS1.C.M(); // ok: C can be accessed through NS1.
        }
    }
}
// File2.cs
namespace NS1
{
    class Program
    {
        C.M(); // error
        NS1.C.M(); // error
    }
}

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

Атрибуты

Локальные для файла классы могут быть типами атрибутов и могут использоваться как атрибуты как в типах, локальных для файла, так и в типах, не локальных для файла, так же, как если бы тип атрибута был не локальным для файла. Имя метаданных типа атрибута file-local по-прежнему формируется с использованием той же стратегии генерации имен, что и другие file-local типы. Это означает, что обнаружение наличия локального типа файла по жестко закодированному имени строки, скорее всего, будет непрактичным, так как оно требуется в зависимости от стратегии создания внутренних имен компилятора, которая может измениться с течением времени. Однако обнаружение с помощью typeof(MyFileLocalAttribute) работает.

using System;
using System.Linq;

file class MyFileLocalAttribute : Attribute { }

[MyFileLocalAttribute]
public class C
{
    public static void Main()
    {
        var attribute = typeof(C).CustomAttributes.Where(attr => attr.AttributeType == typeof(MyFileLocalAttribute)).First();
        Console.Write(attribute); // outputs the generated name of the file-local attribute type
    }
}

Использование в подписях

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

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

Разрешить использование подписи только в членах локальных типов файлов

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

file class FileBase
{
}

public class Derived : FileBase // error
{
    private FileBase M2() => new FileBase() // error
}

file class FileDerived : FileBase // ok
{
    private FileBase M2() => new FileBase(); // ok
}

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

file interface I
{
    void M(I i);
}

class C : I
{
    void I.M(I i) { } // error
}

global using static

Это ошибка на этапе компиляции — использование локального типа файла в директиве global using static, т. е.

global using static C; // error

file class C
{
    public static void M() { }
}

Реализация/переопределения

Объявления типа file-local могут реализовывать интерфейсы, переопределять виртуальные методы и т. д., как и обычные объявления типов.

file struct Widget : IEquatable<Widget>
{
    public bool Equals(Widget other) => true;
}