Freigeben über


Dateilokale Typen

Hinweis

Dieser Artikel ist eine Feature-Spezifikation. Die Spezifikation dient als Designdokument für das Feature. Es enthält vorgeschlagene Spezifikationsänderungen sowie Informationen, die während des Entwurfs und der Entwicklung des Features erforderlich sind. Diese Artikel werden veröffentlicht, bis die vorgeschlagenen Spezifikationsänderungen abgeschlossen und in die aktuelle ECMA-Spezifikation aufgenommen werden.

Es kann einige Abweichungen zwischen der Feature-Spezifikation und der abgeschlossenen Implementierung geben. Diese Unterschiede werden in den entsprechenden Hinweisen zum Language Design Meeting (LDM) erfasst.

Weitere Informationen zum Prozess für die Aufnahme von Funktions-Speclets in den C#-Sprachstandard finden Sie im Artikel zu den Spezifikationen.

Champion Issue: https://github.com/dotnet/csharplang/issues/5529

Zusammenfassung

Lassen Sie einen file-Modifizierer für Typdeklarationen auf oberster Ebene zu. Der Typ existiert nur in der Datei, in der er deklariert ist.

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

Motivation

Unsere Hauptmotivation kommt von Quellgeneratoren. Quellcode-Generatoren funktionieren, indem sie Dateien zur Benutzerkompilierung hinzufügen.

  1. Diese Dateien sollten Implementierungsdetails enthalten können, die vor dem Rest der Kompilierung verborgen sind, aber in der gesamten Datei, in der sie deklariert sind, verwendet werden können.
  2. Wir möchten, dass die Generatoren weniger nach Typnamen suchen müssen, die nicht mit Deklarationen im Benutzenden Code oder Code anderer Generatoren kollidieren.

detailliertes Design

  • Wir fügen den file-Modifizierer zu den folgenden Modifizierersätzen hinzu:
  • Der file-Modifizierer kann nur für einen Typ auf der obersten Ebene verwendet werden.

Wenn ein Typ den file-Modifizierer besitzt, wird er als Typ file-local bezeichnet.

Zugriff

Der file-Modifikator wird nicht als Modifikator für Barrierefreiheit klassifiziert. Es können keine Zugänglichkeits-Modifizierer in Kombination mit file für einen Typ verwendet werden. file wird als unabhängiges Konzept von der Barrierefreiheit behandelt. Da „file-local“-Typen nicht verschachtelt werden können, kann nur die Standardzugänglichkeit internal für file-Typen verwendet werden.

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

Benennung

Die Implementierung garantiert, dass „file-local“-Typen in unterschiedlichen Dateien mit demselben Namen von der Laufzeit unterschieden werden können. Die Zugänglichkeit und der Name des Typs in den Metadaten sind implementierungsdefiniert. Die Absicht ist, dem Compiler die Adaption zukünftiger Funktionen zur Zugriffsbeschränkung in der Runtime zu ermöglichen, die für diese Funktion geeignet sind. Es wird erwartet, dass in der anfänglichen Implementierung eine internal-Zugänglichkeit verwendet wird und ein unaussprechlicher generierter Name verwendet wird, der von der Datei abhängt, in der der Typ deklariert ist.

Suche

Wir ergänzen den Abschnitt Member-Lookup wie folgt (neuer Text in fett):

  • Wenn K Null ist, werden als nächstes alle eingebetteten Typen, deren Deklarationen Typparameter enthalten, entfernt. Wenn K nicht Null ist, werden alle Mitglieder mit einer anderen Anzahl von Typparametern entfernt. Wenn K Null ist, werden Methoden mit Typparametern nicht entfernt, da der Typinferenzprozess (§11.6.3) die Typargumente möglicherweise ableiten kann.
  • Lassen Sie als Nächstes F die Kompiliereinheit sein, die den Ausdruck enthält, in dem der Element-Lookup erfolgt. Alle Elemente, die „file-local“-Typen sind und nicht in F deklariert werden, werden aus der Gruppe entfernt.
  • Als Nächstes, wenn die Gruppe der zugänglichen Member dateilokale Typen enthält, werden alle Member, die keine dateilokalen Typen sind, aus der Gruppe entfernt.

Hinweise

Diese Regeln verbieten die Verwendung von dateilokalen Typen außerhalb der Datei, in der sie deklariert sind.

Mit diesen Regeln kann ein „file-local“-Typ auch ein Shadowing eines Namespace oder eines anderen als eines „non-file“-Typs ausführen:

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

Beachten Sie, dass der Abschnitt Bereiche der Spezifikation nicht aktualisiert wird. Dies liegt an Folgendem (wie in der Spezifikation angegeben):

Der Bereich eines Namens ist die Region des Programmtexts, in dem die Entität, die durch den Namen deklariert wird, referenziert werden kann, ohne den Namen zu qualifizieren.

Tatsächlich wirkt sich der Bereich nur auf den Lookup für nicht qualifizierte Namen aus. Dies ist nicht ganz das richtige Konzept für uns, da wir auch den Lookup qualifizierter Namen berücksichtigen müssen.

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

Daher spezifizieren wir die Funktion nicht in Bezug darauf, in welchem Bereich der Typ enthalten ist, sondern als zusätzliche „Filterungsregeln“ im Element-Lookup.

Attribute

Dateilokale Klassen dürfen Attributtypen sein und können als Attribute sowohl in dateilokalen Typen als auch in nicht-dateilokalen Typen verwendet werden, so als ob der Attributtyp ein nicht-dateilokaler Typ wäre. Der Metadatenname des dateilokalen Attributtyps durchläuft weiterhin die gleiche Strategie zur Generierung von Namen wie andere dateilokale Typen. Dies bedeutet, dass das Erkennen des Vorhandenseins eines dateilokalen Typs mit einem hartcodierten Zeichenfolgennamen wahrscheinlich unpraktisch ist, da dies eine Abhängigkeit von der internen Namensgenerierungsstrategie des Compilers erfordert, die sich im Laufe der Zeit ändern kann. Die Erkennung über typeof(MyFileLocalAttribute) funktioniert jedoch.

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

Verwendung in Signaturen

Es besteht eine allgemeine Notwendigkeit, die Anzeige von „file-local“-Typen in Elementparametern, Rückgaben und Typparametereinschränkungen zu verhindern, wenn der „file-local“-Typ zum Zeitpunkt der Verwendung des Elements möglicherweise nicht im Bereich ist.

Beachten Sie, dass es nicht-dateilokalen Typen erlaubt ist, dateilokale Schnittstellen zu implementieren, ähnlich wie Typen weniger zugängliche Schnittstellen implementieren können. Abhängig von den Typen, die in den Schnittstellenmitgliedern vorhanden sind, kann dies zu einem Verstoß gegen die Regeln im folgenden Abschnitt führen.

Zulassen der Verwendung von Signaturen nur in Elementen von „file-local“-Typen

Dies kann vielleicht am einfachsten dadurch sichergestellt werden, indem durchgesetzt wird, dass die Anzeige von „file-local“-Typen nur in Signaturen oder als Basistypen anderer „file-local“ Typen möglich ist:

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
}

Beachten Sie, dass dies die Verwendung in expliziten Implementierungen einschränkt, auch wenn solche Verwendungen sicher sind. Wir tun dies, um die Regeln für die erste Iteration der Funktion zu vereinfachen.

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

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

global using static

Die Verwendung eines „file-local“-Typs in einer global using static-Direktive ist ein Kompilierfehler, d. h.

global using static C; // error

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

Implementierung/Überschreibungen

Dateilokale Typdeklarationen können wie normale Typdeklarationen Schnittstellen implementieren, virtuelle Methoden überschreiben usw..

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