Udostępnij za pośrednictwem


Typy lokalne plików

Notatka

Ten artykuł jest specyfikacją funkcji. Specyfikacja służy jako dokument projektowy dla funkcji. Zawiera proponowane zmiany specyfikacji wraz z informacjami wymaganymi podczas projektowania i opracowywania funkcji. Te artykuły są publikowane do momentu sfinalizowania proponowanych zmian specyfikacji i włączenia ich do obecnej specyfikacji ECMA.

Mogą wystąpić pewne rozbieżności między specyfikacją funkcji a ukończoną implementacją. Te różnice są uwzględnione w odpowiednich notatkach ze spotkania projektowego języka (LDM).

Więcej informacji na temat procesu wdrażania specyfikacji funkcji można znaleźć w standardzie języka C# w artykule dotyczącym specyfikacji .

Problem dotyczący mistrza: https://github.com/dotnet/csharplang/issues/5529

Streszczenie

Zezwolić na użycie modyfikatora file w deklaracjach typów najwyższego poziomu. Typ istnieje tylko w pliku, w którym jest zadeklarowany.

// 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.

Motywacja

Naszą główną motywacją są generatory kodu źródłowego. Generatory źródeł działają, dodając pliki do kompilacji użytkownika.

  1. Pliki te powinny mieć możliwość przechowywania szczegółów implementacji, które są ukryte w pozostałej części kompilacji, ale mogą być używane w całym pliku, w którym są deklarowane.
  2. Chcemy zmniejszyć zapotrzebowanie generatorów na "wyszukiwanie" nazw typów, które nie będą zderzać się z deklaracjami w kodzie użytkownika lub kodzie z innych generatorów.

Szczegółowy projekt

  • Dodamy modyfikator file do następujących zestawów modyfikatora:
  • Modyfikator file może być używany tylko w typie najwyższego poziomu.

Jeśli typ ma modyfikator file, mówi się, że jest to typ pliku lokalnego.

Dostępność

Modyfikator file nie jest klasyfikowany jako modyfikator ułatwień dostępu. Nie można używać modyfikatorów ułatwień dostępu w połączeniu z file w typie. file jest traktowana jako niezależna koncepcja od ułatwień dostępu. Ponieważ typów lokalnych dla pliku nie można zagnieżdżać, z typami internal można używać tylko domyślnej dostępności file.

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

Nazewnictwo

Implementacja gwarantuje, że typy plików lokalnych w różnych plikach o tej samej nazwie będą unikatowe dla środowiska uruchomieniowego. Dostępność i nazwa typu w metadanych są zależne od implementacji. Celem jest zezwolenie kompilatorowi na przyjęcie wszelkich przyszłych funkcji ograniczeń dostępu w środowisku uruchomieniowym, które są odpowiednie dla tej funkcji. Oczekuje się, że w początkowej implementacji zostanie użyte ułatwienie dostępu internal, a wygenerowana nazwa trudna do wypowiedzenia będzie zależeć od pliku, w którym zadeklarowany jest typ.

Wyszukiwanie

Zmieniamy sekcję wyszukiwania członków w następujący sposób (nowy tekst w pogrubiony):

  • Następnie, jeśli K ma wartość zero, wszystkie zagnieżdżone typy, których deklaracje zawierają parametry typu, zostaną usunięte. Jeśli K nie jest równe zeru, wszyscy członkowie z inną liczbą parametrów typu zostaną usunięci. Jeśli K jest zero, metody o parametrach typu nie są usuwane, ponieważ proces wnioskowania typu (§11.6.3) może być w stanie wywnioskować argumenty typu.
  • Następnie niech F będzie jednostką kompilacji, która zawiera wyrażenie, w którym występuje wyszukiwanie składowych. Wszyscy członkowie, którzy są typami lokalnymi pliku i nie są deklarowani w F, są usuwani z zestawu.
  • Dalej, jeśli zestaw dostępnych elementów członkowskich zawiera typy plików lokalnych, wszystkie elementy członkowskie, które nie są typami plików lokalnych, zostaną usunięte z zestawu.

Uwagi

Te reguły nie zezwalają na użycie typów plików lokalnych poza plikiem, w którym są deklarowane.

Te reguły zezwalają również typowi lokalnemu dla pliku zacieniować przestrzeń nazw lub typ nielokalny dla pliku.

// 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
    }
}

Pamiętaj, że nie aktualizujemy sekcji dotyczącej zakresów w specyfikacji. Dzieje się tak, ponieważ zgodnie z treścią specyfikacji:

Zakres nazwy jest regionem tekstu programu, w którym można odwoływać się do jednostki zadeklarowanej przez nazwę bez kwalifikacji nazwy.

W rezultacie zakres wpływa tylko na wyszukiwanie nazw niekwalifikowanych. Nie jest to właściwa koncepcja, której należy użyć, ponieważ musimy również wpłynąć na wyszukiwanie kwalifikowanych nazw:

// 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
    }
}

W związku z tym nie określamy cechy pod względem zakresu, w którym znajduje się typ, ale raczej jako dodatkowe "reguły filtrowania" w wyszukiwaniu składowych.

Atrybuty

Klasy lokalne plików mogą być typami atrybutów i mogą być używane jako atrybuty zarówno w typach plików lokalnych, jak i innych niż file-local, podobnie jak w przypadku, gdy typ atrybutu był typem nielokacyjnym. Nazwa metadanych typu atrybutu file-local nadal przechodzi przez tę samą strategię generowania nazw co inne typy plików lokalnych. Oznacza to, że wykrywanie obecności lokalnego typu pliku przy użyciu zakodowanej nazwy w postaci ciągu znaków może być niepraktyczne, ponieważ wymaga polegania na wewnętrznej strategii generowania nazw kompilatora, która może ulec zmianie w czasie. Jednak wykrywanie za pośrednictwem typeof(MyFileLocalAttribute) działa.

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
    }
}

Użycie w podpisach

Istnieje ogólna potrzeba uniemożliwienia wyświetlania typów plików lokalnych w parametrach, zwracach i ograniczeniach parametrów typu, w których typ pliku lokalnego może nie znajdować się w zakresie w momencie użycia elementu członkowskiego.

Należy pamiętać, że typy inne niż file-local mogą implementować interfejsy plików lokalnych, podobnie jak typy mogą implementować mniej dostępne interfejsy. W zależności od typów obecnych w elementach członkowskich interfejsu może to spowodować naruszenie reguł w poniższej sekcji.

Zezwalaj tylko na użycie podpisów w członkach typów lokalnych plików

Być może najprostszym sposobem, aby to zapewnić, jest wymuszenie, że typy lokalne dla pliku mogą pojawiać się tylko w sygnaturach lub jako typy bazowe innych typów lokalnych dla pliku.

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
}

Należy pamiętać, że ogranicza to użycie w jawnych implementacjach, mimo że takie użycie jest bezpieczne. Robimy to w celu uproszczenia reguł początkowej iteracji funkcji.

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

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

global using static

Błąd kompilacji występuje, gdy używa się typu zdefiniowanego lokalnie w pliku w dyrektywie global using static, to znaczy

global using static C; // error

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

Implementacja/nadpisania

Deklaracje typów plików lokalnych mogą implementować interfejsy, zastępować metody wirtualne itp., podobnie jak zwykłe deklaracje typów.

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