Freigeben über


Statische abstrakte Member in Schnittstellen

Anmerkung

Dieser Artikel ist eine Featurespezifikation. 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 Featurespezifikation 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.

Zusammenfassung

Eine Schnittstelle darf abstrakte statische Member angeben, für die anschließend implementierende Klassen und Strukturen eine explizite oder implizite Implementierung bereitstellen müssen. Auf die Member kann über Typparameter zugegriffen werden, die durch die Schnittstelle eingeschränkt werden.

Motivation

Es gibt derzeit keine Möglichkeit, über statische Member hinweg zu abstrahieren und generalisierten Code zu schreiben, der für alle Typen gilt, die diese statischen Member definieren. Dies ist besonders problematisch für Memberarten, die nur in statischer Form vorhanden sind, insbesondere Operatoren.

Dieses Feature ermöglicht generische Algorithmen über numerische Typen, dargestellt durch Schnittstelleneinschränkungen, die das Vorhandensein bestimmter Operatoren angeben. Die Algorithmen können daher in Bezug auf solche Operatoren ausgedrückt werden:

// Interface specifies static properties and operators
interface IAddable<T> where T : IAddable<T>
{
    static abstract T Zero { get; }
    static abstract T operator +(T t1, T t2);
}

// Classes and structs (including built-ins) can implement interface
struct Int32 : …, IAddable<Int32>
{
    static Int32 IAddable.operator +(Int32 x, Int32 y) => x + y; // Explicit
    public static int Zero => 0;                          // Implicit
}

// Generic algorithms can use static members on T
public static T AddAll<T>(T[] ts) where T : IAddable<T>
{
    T result = T.Zero;                   // Call static operator
    foreach (T t in ts) { result += t; } // Use `+`
    return result;
}

// Generic method can be applied to built-in and user-defined types
int sixtyThree = AddAll(new [] { 1, 2, 4, 8, 16, 32 });

Syntax

Schnittstellenmitglieder

Das Feature würde es ermöglichen, statische Schnittstellenmitglieder als virtuell zu deklarieren.

Regeln vor C# 11

Vor C# 11 sind Instanzmember in Schnittstellen implizit abstrakt (oder virtuell, wenn es eine Standardimplementierung gibt), können jedoch optional einen abstract-Modifizierer (oder virtual-Modifizierer) aufweisen. Nicht-virtuelle Instanzmember müssen explizit als sealedgekennzeichnet werden.

Statische Schnittstellenmember sind derzeit implizit nicht-virtuell und lassen keine abstract-, virtual- oder sealed-Modifizierer zu.

Vorschlag

Abstrakte statische Elemente

Andere statische Schnittstellenelemente als Felder dürfen auch den abstract-Modifizierer verwenden. Abstrakte statische Member dürfen keinen Körper haben (im Fall von Eigenschaften dürfen die Accessoren keinen Körper haben).

interface I<T> where T : I<T>
{
    static abstract void M();
    static abstract T P { get; set; }
    static abstract event Action E;
    static abstract T operator +(T l, T r);
    static abstract bool operator ==(T l, T r);
    static abstract bool operator !=(T l, T r);
    static abstract implicit operator T(string s);
    static abstract explicit operator string(T t);
}
Virtuelle statische Member

Statische Schnittstellenelemente außer Feldern dürfen ebenfalls den virtual-Modifizierer haben. Virtuelle statische Member müssen einen Körper haben.

interface I<T> where T : I<T>
{
    static virtual void M() {}
    static virtual T P { get; set; }
    static virtual event Action E;
    static virtual T operator +(T l, T r) { throw new NotImplementedException(); }
}
Explizit nicht-virtuelle statische Member

Zur Wahrung der Symmetrie mit nicht-virtuellen Instanzmembern sollten statische Member (mit Ausnahme von Feldern) einen optionalen sealed-Modifizierer zulassen, auch wenn sie standardmäßig nicht-virtuell sind:

interface I0
{
    static sealed void M() => Console.WriteLine("Default behavior");
    
    static sealed int f = 0;
    
    static sealed int P1 { get; set; }
    static sealed int P2 { get => f; set => f = value; }
    
    static sealed event Action E1;
    static sealed event Action E2 { add => E1 += value; remove => E1 -= value; }
    
    static sealed I0 operator +(I0 l, I0 r) => l;
}

Implementierung von Schnittstellenmembern

Aktuelle Regeln

Klassen und Strukturen können abstrakte Instanzmitglieder von Schnittstellen entweder implizit oder explizit implementieren. Ein implizit implementiertes Schnittstellenmember ist eine normale (virtuelle oder nicht-virtuelle) Memberdeklaration der Klasse oder Struktur, die „zufällig“ auch das Schnittstellenmember implementiert. Das Member kann sogar von einer Basisklasse geerbt werden und muss daher in der Klassendeklaration noch nicht einmal vorhanden sein.

Ein explizit implementiertes Schnittstellenmitglied verwendet einen qualifizierten Namen, um das betreffende Schnittstellenelement zu identifizieren. Auf die Implementierung kann nicht direkt als Mitglied der Klasse oder Struktur zugegriffen werden, sondern nur über die Schnittstelle.

Vorschlag

Es ist keine neue Syntax in Klassen und Strukturen erforderlich, um die implizite Implementierung statischer abstrakter Schnittstellenmitglieder zu erleichtern. Vorhandene statische Memberdeklarationen dienen diesem Zweck.

Explizite Implementierungen statischer abstrakter Schnittstellenmember verwenden einen qualifizierten Namen zusammen mit dem static-Modifizierer.

class C : I<C>
{
    string _s;
    public C(string s) => _s = s;
    static void I<C>.M() => Console.WriteLine("Implementation");
    static C I<C>.P { get; set; }
    static event Action I<C>.E // event declaration must use field accessor syntax
    {
        add { ... }
        remove { ... }
    }
    static C I<C>.operator +(C l, C r) => new C($"{l._s} {r._s}");
    static bool I<C>.operator ==(C l, C r) => l._s == r._s;
    static bool I<C>.operator !=(C l, C r) => l._s != r._s;
    static implicit I<C>.operator C(string s) => new C(s);
    static explicit I<C>.operator string(C c) => c._s;
}

Semantik

Einschränkungen für Operatoren

Aktuell müssen alle unären und binären Operatordeklarationen die Anforderung erfüllen, dass mindestens ein Operand den Typ T oder T? hat, wobei T der Instanztyp des einschließenden Typs ist.

Diese Anforderungen müssen gelockert werden, damit ein eingeschränkter Operand einen Typparameter haben kann, der als „Instanztyp des einschließenden Typs“ zählt.

Damit ein Typparameter T als „Instanztyp des einschließenden Typs“ zählen kann, muss er die folgenden Anforderungen erfüllen:

  • T ist ein direkter Typparameter in der Schnittstelle, in der die Operatordeklaration enthalten ist, und
  • T ist direkt durch den „Instanztyp“ wie in der Spezifikation genannt eingeschränkt, d. h. die umgebende Schnittstelle mit ihren eigenen Typparametern, die als Typargumente verwendet werden.

Gleichheitsoperatoren und Umwandlungen

Abstrakte/virtuelle Deklarationen von ==- und != Operatoren sowie abstrakte/virtuelle Deklarationen impliziter und expliziter Konvertierungsoperatoren sind in Schnittstellen zulässig. Abgeleitete Schnittstellen werden ebenfalls in der Lage sein, sie zu implementieren.

Für die Operatoren == und != muss mindestens ein Parametertyp ein Typparameter sein, der als „Instanztyp des einschließenden Typs“ zählt, wie im vorherigen Abschnitt definiert.

Implementieren statischer abstrakter Member

Die Regeln dafür, wann eine statische Memberdeklaration in einer Klasse oder Struktur als Implementierung eines statischen abstrakten Schnittstellenmembers betrachtet wird, und die entsprechenden Anforderungen sind mit den Regeln und Anforderungen von Instanzmembern identisch.

TBD: Hier sind möglicherweise zusätzliche oder unterschiedliche Regeln erforderlich, an die wir noch nicht gedacht haben.

Schnittstellen als Typargumente

Wir haben das von https://github.com/dotnet/csharplang/issues/5955 angesprochene Problem erörtert und beschlossen, eine Einschränkung für die Verwendung einer Schnittstelle als Typargument (https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-28.md#type-hole-in-static-abstracts) hinzuzufügen. Hier ist die Einschränkung, wie sie von https://github.com/dotnet/csharplang/issues/5955 vorgeschlagen und von der LDM genehmigt wurde.

Eine Schnittstelle, die ein statisches abstraktes/virtuelles Member enthält oder erbt und nicht die jeweils spezifischste Implementierung in der Schnittstelle besitzt, kann nicht als Typargument verwendet werden. Wenn alle statischen abstrakten/virtuellen Member die jeweils spezifischste Implementierung besitzen, kann die Schnittstelle als Typargument verwendet werden.

Zugreifen auf statische abstrakte Schnittstellenmitglieder

Auf ein statisches abstraktes Schnittstellenmember M kann in einem Typparameter T mithilfe des Ausdrucks T.M zugegriffen werden, wenn T durch eine Schnittstelle I eingeschränkt wird und M ein zugängliches statisches abstraktes Member von I ist.

T M<T>() where T : I<T>
{
    T.M();
    T t = T.P;
    T.E += () => { };
    return t + T.P;
}

Zur Laufzeit ist die tatsächlich verwendete Memberimplementierung diejenige, die im tatsächlichen Typ vorhanden ist, der als Typargument angegeben ist.

C c = M<C>(); // The static members of C get called

Da Abfrageausdrücke als syntaktische Umschreibung spezifiziert sind, können Sie in C# einen Typ als Abfragequelle verwenden, solange er statische Member für die von Ihnen verwendeten Abfrageoperatoren bereitstellt. Anders ausgedrückt: Wenn die Syntax passt, lassen wir dies zu. Wir glauben, dass dieses Verhalten in der ursprünglichen Version von LINQ nicht beabsichtigt oder wichtig war, und wir möchten den Aufwand nicht betreiben, es für Typparameter zu unterstützen. Wenn es Szenarien gibt, werden wir von ihnen erfahren und können entscheiden, ob wir sie später behandeln.

Varianzsicherheit §18.2.3.2

Varianzsicherheitsregeln sollten auf Signaturen statischer abstrakter Elemente angewendet werden. Die in https://github.com/dotnet/csharplang/blob/main/proposals/variance-safety-for-static-interface-members.md#variance-safety vorgeschlagene Addition sollte geändert werden:

Diese Einschränkungen gelten nicht für Typen innerhalb von Deklarationen statischer Member.

Bis

Diese Einschränkungen gelten nicht für Typen in Deklarationen von nicht-virtuellen, nicht-abstrakten statischen Membern.

§10.5.4 Benutzerdefinierte implizite Konvertierungen

Die folgenden Aufzählungspunkte

  • Bestimmen Sie die Typen S, S₀ und T₀.
    • Wenn E einen Typ aufweist, lassen Sie S diesen Typ sein.
    • Wenn S oder T Nullwerttypen sind, sollten Sᵢ und Tᵢ die zugrunde liegenden Typen sein. Andernfalls sollten Sᵢ und Tᵢ S bzw. T sein.
    • Wenn Sᵢ oder Tᵢ Typparameter sind, sollen S₀ und T₀ als ihre effektiven Basisklassen fungieren, andernfalls sollen S₀ und T₀ entsprechend Sₓ und Tᵢsein.
  • Suchen Sie die Gruppe von Typen, D, aus der benutzerdefinierte Umwandlungsoperatoren berücksichtigt werden. Dieser Satz besteht aus S0 (wenn S0 eine Klasse oder Struktur ist), den Basisklassen von S0 (wenn S0 eine Klasse ist) und T0 (wenn T0 eine Klasse oder Struktur ist).
  • Ermitteln Sie die Gruppe anwendbarer benutzerdefinierter und gelifteter Umwandlungsoperatoren, U. Dieser Satz besteht aus den benutzerdefinierten und gelifteten impliziten Umwandlungsoperatoren, die von den Klassen oder Strukturen in D deklariert werden und aus einem Typ, der S einschließt, in einen Typ, der T einschließt, umgewandelt werden. Wenn U leer ist, ist die Konvertierung nicht definiert, und ein Kompilierungszeitfehler tritt auf.

werden wie folgt geändert:

  • Bestimmen Sie die Typen S, S₀ und T₀.
    • Wenn E einen Typ aufweist, lassen Sie S diesen Typ sein.
    • Wenn S oder T Nullwerttypen sind, sollten Sᵢ und Tᵢ die zugrunde liegenden Typen sein. Andernfalls sollten Sᵢ und Tᵢ S bzw. T sein.
    • Wenn Sᵢ oder Tᵢ Typparameter sind, dann sind S₀ und T₀ ihre effektiven Basisklassen; andernfalls sind S₀ und T₀Sₓ bzw. Tᵢ.
  • Ermitteln Sie die Gruppe anwendbarer benutzerdefinierter und gelifteter Umwandlungsoperatoren, U.
    • Suchen Sie die Gruppe von Typen, D1, aus der benutzerdefinierte Umwandlungsoperatoren berücksichtigt werden. Dieser Satz besteht aus S0 (wenn S0 eine Klasse oder Struktur ist), den Basisklassen von S0 (wenn S0 eine Klasse ist) und T0 (wenn T0 eine Klasse oder Struktur ist).
    • Ermitteln Sie die Gruppe anwendbarer benutzerdefinierter und gelifteter Umwandlungsoperatoren, U1. Dieser Satz besteht aus den benutzerdefinierten und gelifteten impliziten Umwandlungsoperatoren, die von den Klassen oder Strukturen in D1 deklariert werden und aus einem Typ, der S einschließt, in einen Typ, der T einschließt, umgewandelt werden.
    • Wenn U1 nicht leer ist, ist UU1. Andernfalls,
      • Suchen Sie die Gruppe von Typen, D2, aus der benutzerdefinierte Umwandlungsoperatoren berücksichtigt werden. Dieser Satz besteht aus Sᵢeffektiver Schnittstellensatz und deren Basisschnittstellen (wenn Sᵢ ein Typparameter ist) und Tᵢeffektiver Schnittstellensatz (wenn Tᵢ ein Typparameter ist).
      • Ermitteln Sie die Gruppe anwendbarer benutzerdefinierter und gelifteter Umwandlungsoperatoren, U2. Dieser Satz besteht aus den benutzerdefinierten und gelifteten impliziten Umwandlungsoperatoren, die von den Schnittstellen in D2 deklariert werden und aus einem Typ, der S einschließt, in einen Typ, der T einschließt, umgewandelt werden.
      • Wenn U2 nicht leer ist, ist UU2
  • Wenn U leer ist, ist die Konvertierung nicht definiert, und ein Kompilierungszeitfehler tritt auf.

§10.3.9 Benutzerdefinierte explizite Umwandlungen

Die folgenden Aufzählungspunkte

  • Bestimmen Sie die Typen S, S₀ und T₀.
    • Wenn E einen Typ aufweist, lassen Sie S diesen Typ sein.
    • Wenn S oder T Nullwerttypen sind, sollten Sᵢ und Tᵢ die zugrunde liegenden Typen sein. Andernfalls sollten Sᵢ und Tᵢ S bzw. T sein.
    • Wenn Sᵢ oder Tᵢ Typparameter sind, sollten S₀ und T₀ ihre effektiven Basisklassen sein. Andernfalls sollten S₀ und T₀ Sᵢ bzw. Tᵢ sein.
  • Suchen Sie die Gruppe von Typen, D, aus der benutzerdefinierte Umwandlungsoperatoren berücksichtigt werden. Dieser Satz besteht aus S0 (wenn S0 eine Klasse oder Struktur ist), die Basisklassen S0 (wenn S0 eine Klasse ist), T0 (wenn T0 eine Klasse oder Struktur ist) und die Basisklassen von T0 (wenn T0 eine Klasse ist).
  • Ermitteln Sie die Gruppe anwendbarer benutzerdefinierter und gelifteter Umwandlungsoperatoren, U. Diese Gruppe besteht aus den benutzerdefinierten und gelifteten impliziten oder expliziten Umwandlungsoperatoren, die von den Klassen oder Strukturen in D deklariert werden und aus einem Typ, der S einschließt oder hiervon eingeschlossen wird, in einen Typ umgewandelt werden, der T einschließt oder hiervon eingeschlossen wird. Wenn U leer ist, ist die Konvertierung nicht definiert, und ein Kompilierungszeitfehler tritt auf.

werden wie folgt geändert:

  • Bestimmen Sie die Typen S, S₀ und T₀.
    • Wenn E einen Typ aufweist, lassen Sie S diesen Typ sein.
    • Wenn S oder T Nullwerttypen sind, sollten Sᵢ und Tᵢ die zugrunde liegenden Typen sein. Andernfalls sollten Sᵢ und Tᵢ S bzw. T sein.
    • Wenn Sᵢ oder Tᵢ Typparameter sind, sollen S₀ und T₀ ihre effektiven Basisklassen sein; andernfalls sollen S₀ und T₀Sᵢ bzw. Tᵢsein.
  • Ermitteln Sie die Gruppe anwendbarer benutzerdefinierter und gelifteter Umwandlungsoperatoren, U.
    • Suchen Sie die Gruppe von Typen, D1, aus der benutzerdefinierte Umwandlungsoperatoren berücksichtigt werden. Dieser Satz besteht aus S0 (wenn S0 eine Klasse oder Struktur ist), die Basisklassen S0 (wenn S0 eine Klasse ist), T0 (wenn T0 eine Klasse oder Struktur ist) und die Basisklassen von T0 (wenn T0 eine Klasse ist).
    • Ermitteln Sie die Gruppe anwendbarer benutzerdefinierter und gelifteter Umwandlungsoperatoren, U1. Diese Gruppe besteht aus den benutzerdefinierten und gelifteten impliziten oder expliziten Umwandlungsoperatoren, die von den Klassen oder Strukturen in D1 deklariert werden und aus einem Typ, der S einschließt oder hiervon eingeschlossen wird, in einen Typ umgewandelt werden, der T einschließt oder hiervon eingeschlossen wird.
    • Wenn U1 nicht leer ist, ist UU1. Andernfalls,
      • Suchen Sie die Gruppe von Typen, D2, aus der benutzerdefinierte Umwandlungsoperatoren berücksichtigt werden. Diese Gruppe besteht aus Sᵢeffektiven Schnittstellensätzen und ihren Basisschnittstellen (wenn Sᵢ ein Typparameter ist) sowie aus Tᵢeffektiven Schnittstellensätzen und ihren Basisschnittstellen (wenn Tᵢ ein Typparameter ist).
      • Ermitteln Sie die Gruppe anwendbarer benutzerdefinierter und gelifteter Umwandlungsoperatoren, U2. Diese Gruppe besteht aus den benutzerdefinierten und gelifteten impliziten oder expliziten Umwandlungsoperatoren, die von den Schnittstellen in D2 deklariert werden und aus einem Typ, der S einschließt oder hiervon eingeschlossen wird, in einen Typ umgewandelt werden, der T einschließt oder hiervon eingeschlossen wird.
      • Wenn U2 nicht leer ist, ist UU2
  • Wenn U leer ist, ist die Konvertierung nicht definiert, und ein Kompilierungszeitfehler tritt auf.

Standardimplementierungen

Ein zusätzliches Merkmal dieses Vorschlags besteht darin, dass statischen virtuellen Membern in Schnittstellen Standardimplementierungen ermöglicht werden, wie dies bereits bei virtuellen/abstrakten Instanzmembern der Fall ist.

Eine Komplikation hier ist, dass Standardimplementierungen andere statische virtuelle Member "virtuell" aufrufen möchten. Der direkte Aufruf von statischen virtuellen Membern auf der Schnittstelle würde die Übermittlung eines verborgenen Typparameters erfordern, der den „self“-Typ darstellt, von dem die aktuelle statische Methode tatsächlich aufgerufen wird. Dies scheint kompliziert, teuer und potenziell verwirrend zu sein.

Wir haben über eine einfachere Version diskutiert, die die Einschränkungen des aktuellen Vorschlags wahrt, dass statische virtuelle Member nur auf Typparametern aufgerufen werden können. Da Schnittstellen mit statischen virtuellen Membern häufig einen expliziten Typparameter haben, der einen "Selbsttyp" repräsentiert, wäre das kein großer Verlust: Andere statische virtuelle Member könnten einfach auf diesem Selbsttyp aufgerufen werden. Diese Version ist viel einfacher und scheint ziemlich machbar.

In https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-24.md#default-implementations-of-abstract-statics haben wir beschlossen, Standardimplementierungen statischer Member zu unterstützen, die den in https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/default-interface-methods.md festgelegten Regeln folgen und entsprechend erweitert werden.

Musterabgleich

Für den folgenden Code erwarten Benutzer möglicherweise, dass er „True“ druckt (als ob das konstante Muster inline geschrieben würde):

M(1.0);

static void M<T>(T t) where T : INumberBase<T>
{
    Console.WriteLine(t is 1); // Error. Cannot use a numeric constant
    Console.WriteLine((t is int i) && (i is 1)); 
}

Da der Eingabetyp des Musters jedoch nicht double ist, führt das konstante Muster 1 zuerst eine Typprüfung des eingehenden T auf int aus. Da dies nicht intuitiv ist, wird dies blockiert ist, bis eine zukünftige C#-Version ein besseres Handling für den numerischen Abgleich mit Typen bereitstellt, die von INumberBase<T> abgeleitet werden. Dazu werden wir sagen, dass wir INumberBase<T> explizit als Typ erkennen, von dem alle "Zahlen" abgeleitet werden, und das Muster blockieren, wenn wir versuchen, ein numerisches Konstantenmuster mit einem Zahlentyp abzugleichen, in dem das Muster nicht dargestellt werden kann (d. h. ein Typparameter, der auf INumberBase<T>beschränkt ist, oder einen benutzerdefinierten Zahlentyp, der von INumberBase<T>erbt).

Formal fügen wir eine Ausnahme zur Definition der Musterkompatibilität für konstante Muster hinzu:

Ein Konstantenmuster vergleicht den Wert eines Ausdrucks mit einem konstanten Wert. Die Konstante kann ein beliebiger Konstantenausdruck sein, z. B. ein Literal, der Name einer deklarierten const-Variablen oder eine Enumerationskonstante. Wenn der Eingabewert kein offener Typ ist, wird der konstante Ausdruck implizit in den Typ des übereinstimmenden Ausdrucks umgewandelt. Wenn der Typ des Eingabewerts nicht musterkompatibel mit dem Typ des konstanten Ausdrucks ist, endet der Musterabgleich mit einem Fehler. Wenn der abgeglichene konstante Ausdruck ein numerischer Wert ist, der Eingabewert ein Typ ist, der von System.Numerics.INumberBase<T> erbt, und es keine konstante Umwandlung aus dem konstanten Ausdruck in den Typ des Eingabewerts gibt, endet der Musterabgleich mit einem Fehler.

Außerdem fügen wir eine ähnliche Ausnahme für relationale Muster hinzu:

Wenn es sich bei der Eingabe um einen Typ handelt, für den ein geeigneter eingebauter binärer relationaler Operator definiert ist, der mit der Eingabe als linkem Operand und der angegebenen Konstante als rechtem Operanden anwendbar ist, gilt die Auswertung dieses Operators als Bedeutung des relationalen Musters. Andernfalls wandeln wir mithilfe einer expliziten Nullwert- oder Unboxing-Konvertierung die Eingabe in den Typ des Ausdrucks um. Es handelt sich um einen Kompilierungszeitfehler, wenn keine solche Konvertierung vorhanden ist. Es handelt sich um einen Kompilierzeitfehler, wenn der Eingabetyp ein Typparameter ist, der auf System.Numerics.INumberBase<T> beschränkt ist, oder ein Typ, der von System.Numerics.INumberBase<T> erbt, und wenn der Eingabetyp keinen geeigneten integrierten binären relationalen Operator definiert hat. Das Muster wird als nicht übereinstimmend betrachtet, wenn die Konvertierung fehlschlägt. Wenn die Konvertierung erfolgreich ist, ist das Ergebnis des Musterabgleichsvorgangs das Ergebnis der Auswertung des Ausdrucks e OP v, wobei e die konvertierte Eingabe ist, OP der relationale Operator und v ist der konstante Ausdruck.

Nachteile

  • Das Konzept „statisch abstrakt“ ist neu und stellt einen relevanten Beitrag zum Konzept von C# dar.
  • Es ist kein einfaches Konzept. Wir sollten sicherstellen, dass es sich lohnt.

Alternativen

Strukturelle Einschränkungen

Ein alternativer Ansatz wäre, "strukturelle Einschränkungen" direkt zu haben und explizit das Vorhandensein bestimmter Operatoren für einen Typparameter zu verlangen. Die Nachteile davon sind: - Dies müsste jedes Mal ausgeschrieben werden. Eine benannte Einschränkung scheint besser zu sein. - Dies ist eine ganz neue Art von Einschränkung, während das vorgeschlagene Feature das vorhandene Konzept von Schnittstelleneinschränkungen verwendet. – Es funktioniert nur für Operatoren, nicht (jedenfalls nicht auf einfache Weise) für andere Arten statischer Member.

Ungelöste Fragen

Statische abstrakte Schnittstellen und statische Klassen

Weitere Informationen finden Sie unter https://github.com/dotnet/csharplang/issues/5783 und https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-16.md#static-abstract-interfaces-and-static-classes.

Designbesprechungen