Freigeben über


Benutzerdefinierte Verbundzuordnungsoperatoren

Hinweis

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 relevanten Sprachentwurfsbesprechungen (LDM)-Notizen erfasst.

Weitere Informationen zum Einführen von Featurespezifikationen in den C#-Sprachstandard finden Sie im Artikel zu den Spezifikationen.

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

Zusammenfassung

Zulassen, dass Benutzertypen das Verhalten zusammengesetzter Zuordnungsoperatoren so anpassen können, dass das Ziel der Zuordnung an Ort und Stelle geändert wird.

Motivation

C# bietet Unterstützung für die Implementierungen des Entwicklerüberladungsoperators für benutzerdefinierten Typ. Darüber hinaus bietet sie Unterstützung für "Zusammengesetzte Zuordnungsoperatoren", die es dem Benutzer ermöglichen, Code ähnlich x += y zu schreiben als x = x + y. Allerdings lässt die Sprache derzeit nicht zu, dass der Entwickler diese zusammengesetzten Zuordnungsoperatoren überladen kann, und während das Standardverhalten das richtige tut, insbesondere wenn es sich um unveränderliche Werttypen handelt, ist es nicht immer "optimal".

Im folgenden Beispiel

class C1
{
    static void Main()
    {
        var c1 = new C1();
        c1 += 1;
        System.Console.Write(c1);
    }
    
    public static C1 operator+(C1 x, int y) => new C1();
}

mit den aktuellen Sprachregeln ruft der Zusammengesetzte Zuordnungsoperator c1 += 1 einen benutzerdefinierten + Operator auf und weist dann den Rückgabewert der lokalen Variablen c1zu. Beachten Sie, dass die Operatorimplementierung eine neue Instanz von C1, während aus Der Sicht des Verbrauchers eine direkte Änderung an der ursprünglichen Instanz der C1 stattdessen so gut funktioniert (sie wird nicht nach der Zuordnung verwendet), mit einem zusätzlichen Vorteil, eine zusätzliche Zuordnung zu vermeiden.

Wenn ein Programm einen zusammengesetzten Zuordnungsvorgang verwendet, ist der häufigste Effekt, dass der ursprüngliche Wert "verloren" ist und nicht mehr für das Programm verfügbar ist. Bei Typen, die große Daten haben (z. B. BigInteger, Tensors usw.), sind die Kosten für die Erstellung eines neuen Ziels, iterieren und Kopieren des Speichers tendenziell ziemlich teuer. Eine direkte Mutation würde es ermöglichen, diese Ausgaben in vielen Fällen zu überspringen, was erhebliche Verbesserungen für solche Szenarien bieten kann.

Daher kann es von Vorteil sein, dass Benutzertypen das Verhalten von Verbundzuordnungsoperatoren anpassen und Szenarien optimieren können, die andernfalls zuordnen und kopieren müssen.

Detailliertes Design

Syntax

Die Grammatik bei "at https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15101-general " wird wie folgt angepasst.

Operatoren werden mit operator_declarations deklariert:

operator_declaration
    : attributes? operator_modifier+ operator_declarator operator_body
    ;

operator_modifier
    : 'public'
    | 'static'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    | 'abstract'
    | 'virtual'
    | 'sealed'
+   | 'override'
+   | 'new'
+   | 'readonly'
    ;

operator_declarator
    : unary_operator_declarator
    | binary_operator_declarator
    | conversion_operator_declarator
+   | increment_operator_declarator
+   | compound_assignment_operator_declarator
    ;

unary_operator_declarator
    : type 'operator' overloadable_unary_operator '(' fixed_parameter ')'
    ;

logical_negation_operator
    : '!'
    ;

overloadable_unary_operator
-   : '+' | 'checked'? '-' | logical_negation_operator | '~' | 'checked'? '++' | 'checked'? '--' | 'true' | 'false'
+   : '+' | 'checked'? '-' | logical_negation_operator | '~' | 'true' | 'false'
    ;

binary_operator_declarator
    : type 'operator' overloadable_binary_operator
        '(' fixed_parameter ',' fixed_parameter ')'
    ;

overloadable_binary_operator
    : 'checked'? '+'  | 'checked'? '-'  | 'checked'? '*'  | 'checked'? '/'  | '%'  | '&' | '|' | '^'  | '<<'
    | right_shift | '==' | '!=' | '>' | '<' | '>=' | '<='
    ;

conversion_operator_declarator
    : 'implicit' 'operator' type '(' fixed_parameter ')'
    | 'explicit' 'operator' type '(' fixed_parameter ')'
    ;

+increment_operator_declarator
+   : type 'operator' overloadable_increment_operator '(' fixed_parameter ')'
+   | 'void' 'operator' overloadable_increment_operator '(' ')'
+   ;

+overloadable_increment_operator
+   : 'checked'? '++' | 'checked'? '--'
+    ;

+compound_assignment_operator_declarator
+   : 'void' 'operator' overloadable_compound_assignment_operator
+       '(' fixed_parameter ')'
+   ;

+overloadable_compound_assignment_operator
+   : 'checked'? '+=' | 'checked'? '-=' | 'checked'? '*=' | 'checked'? '/=' | '%=' | '&=' | '|=' | '^=' | '<<='
+   | right_shift_assignment
+   | unsigned_right_shift_assignment
+   ;

operator_body
    : block
    | '=>' expression ';'
    | ';'
    ;

Es gibt fünf Kategorien überladener Operatoren: unäre Operatoren, binäre Operatoren, Konvertierungsoperatoren, Inkrementoperatoren, Zusammengesetzte Zuordnungsoperatoren.

Die folgenden Regeln gelten für alle Operatordeklarationen:

  • Eine Betreibererklärung enthält static Modifizierer.

Zusammengesetzte Zuordnungs- und Instanzinkrementoperatoren können Operatoren ausblenden, die in einer Basisklasse deklariert sind. Daher ist der folgende Absatz nicht mehr korrekt und sollte entsprechend angepasst werden, oder er kann entfernt werden:

Da Operatordeklarationen immer die Klasse oder Anweisung erfordern, an der der Operator deklariert wird, um an der Signatur des Operators teilzunehmen, ist es nicht möglich, dass ein in einer abgeleiteten Klasse deklarierter Operator einen in einer Basisklasse deklarierten Operator ausblenden kann. Daher ist der new Modifizierer niemals erforderlich und daher in einer Operatordeklaration nie zulässig.

Unäre Operatoren

Siehe https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15102-unary-operators.

Eine Betreibererklärung enthält einen static Zusatzbezeichner und darf keinen override Zusatz enthalten.

Der folgende Aufzählungspunkt wird entfernt:

  • Ein unärer ++- oder ---Operator muss einen einzelnen Parameter des Typs T oder T? annehmen und denselben Typ oder einen davon abgeleiteten Typ zurückgeben.

Der folgende Absatz wird an keine Erwähnungs- und ++ Operatortoken mehr -- angepasst:

Die Signatur eines unären Operators besteht aus dem Operatortoken (+, , -!~++--trueoder false) und dem Typ des einzelnen Parameters. Der Rückgabetyp ist weder Teil der Signatur eines unären Operators noch der Name des Parameters.

Ein Beispiel im Abschnitt sollte so angepasst werden, dass kein benutzerdefinierter Inkrementoperator verwendet wird.

Binäre Operatoren

Siehe https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15103-binary-operators.

Eine Betreibererklärung enthält einen static Zusatzbezeichner und darf keinen override Zusatz enthalten.

Konvertierungsoperatoren

Siehe https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15104-conversion-operators.

Eine Betreibererklärung enthält einen static Zusatzbezeichner und darf keinen override Zusatz enthalten.

Inkrementierungsoperatoren

Die folgenden Regeln gelten für statische Inkrementierungsoperatordeklarationen, wobei T der Instanztyp der Klasse oder Struktur, die die Operatordeklaration enthält, bezeichnet wird:

  • Eine Betreibererklärung enthält einen static Zusatzbezeichner und darf keinen override Zusatz enthalten.
  • Ein Operator nimmt einen einzigen Parameter vom Typ oder TT? gibt diesen Typ oder einen daraus abgeleiteten Typ zurück.

Die Signatur eines statischen Inkrementoperators besteht aus den Operatortoken ('checked'? ++, 'checked'? --) und dem Typ des einzelnen Parameters. Der Rückgabetyp ist weder Teil der Signatur eines statischen Inkrementoperators noch der Name des Parameters.

Statische Inkrementierungsoperatoren sind sehr ähnlich wie unäre Operatoren.

Die folgenden Regeln gelten für Instanzinkrementierungsoperatordeklarationen:

  • Eine Betreibererklärung darf keinen Zusatz enthalten static .
  • Ein Operator darf keine Parameter annehmen.
  • Ein Operator hat void rückgabetyp.

Effektiv ist ein Instanzinkrementoperator eine void returning instance method that has no parameters and has a special name in metadata.

Die Signatur eines Instanz-Inkrement-Operators besteht aus den Token des Operators (‚checked‘? ‚++‘ | ‚checked‘? ‚--‘).

Eine checked operator Deklaration erfordert eine paarweise Deklaration einer regular operator. Andernfalls tritt ein Kompilierfehler auf. Siehe auch https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/checked-user-defined-operators.md#semantics.

Der Zweck der Methode besteht darin, den Wert der Instanz an das Ergebnis des angeforderten Inkrementvorgangs anzupassen, unabhängig davon, was im Kontext des deklarierenden Typs bedeutet.

Beispiel:

class C1
{
    public int Value;

    public void operator ++()
    {
        Value++;
    }
}

Ein Instanzinkrementoperator kann einen Operator mit der gleichen Signatur außer Kraft setzen, die in einer Basisklasse deklariert ist, ein override Modifizierer kann zu diesem Zweck verwendet werden.

Die folgenden „reservierten“ Spezialnamen sollten in ECMA-335 aufgenommen werden, um Instanz-Versionen von Inkrement/Dekrement-Operatoren zu unterstützen: | Name | Operator | | -----| -------- | |op_DecrementAssignment| -- | |op_IncrementAssignment| ++ | |op_CheckedDecrementAssignment| checked -- | |op_CheckedIncrementAssignment| checked ++ |

Zusammengesetzte Zuordnungsoperatoren

Die folgenden Regeln gelten für Zusammengesetzte Zuordnungsoperatordeklarationen:

  • Eine Betreibererklärung darf keinen Zusatz enthalten static .
  • Ein Operator muss einen Parameter annehmen.
  • Ein Operator hat void rückgabetyp.

Effektiv ist ein verbundzuordnungsoperator eine void returning instance method that takes one parameter and has a special name in metadata.

Die Signatur eines zusammengesetzten Zuweisungsoperators besteht aus den Operator-Token ('checked'? '+=', 'checked'? '-=', 'checked'? '*=', 'checked'? '/=', '%=', '&=', '|=', '^=', '<<=', right_shift_assignment, unsigned_right_shift_assignment) und dem Typ des einzelnen Parameters. Der Name des Parameters ist nicht Teil der Signatur eines Zusammengesetzten Zuordnungsoperators.

Eine checked operator Deklaration erfordert eine paarweise Deklaration einer regular operator. Andernfalls tritt ein Kompilierfehler auf. Siehe auch https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/checked-user-defined-operators.md#semantics.

Der Zweck der Methode besteht darin, den Wert der Instanz an das Ergebnis anzupassen <instance> <binary operator token> parameter.

Beispiel:

class C1
{
    public int Value;

    public void operator +=(int x)
    {
        Value+=x;
    }
}

Ein Zusammengesetzter Zuordnungsoperator kann einen Operator mit der gleichen Signatur außer Kraft setzen, die in einer Basisklasse deklariert ist, ein override Modifizierer kann zu diesem Zweck verwendet werden.

ECMA-335 hat bereits die folgenden speziellen Namen für benutzerdefinierte Inkrement-Operatoren „reserviert“: | Name | Operator | | -----| -------- | |op_AdditionAssignment|'+=' | |op_SubtractionAssignment|'-=' | |op_MultiplicationAssignment|'*=' | |op_DivisionAssignment|'/=' | |op_ModulusAssignment|'%=' | |op_BitwiseAndAssignment|'&=' | |op_BitwiseOrAssignment|'|=' | |op_ExclusiveOrAssignment|'^=' | |op_LeftShiftAssignment|'<<='| |op_RightShiftAssignment| right_shift_assignment| |op_UnsignedRightShiftAssignment|unsigned_right_shift_assignment|

Es gibt jedoch an, dass die CLS-Kompatibilität erfordert, dass die Operatormethoden nicht ungültige statische Methoden mit zwei Parametern sein, d. h. entspricht den binären C#-Operatoren. Wir sollten die CLS-Complianceanforderungen entspannen, damit die Operatoren keine Instanzmethoden mit einem einzigen Parameter zurückgeben können.

Die folgenden Namen sollten hinzugefügt werden, um geprüfte Versionen der Operatoren zu unterstützen: | Name | Operator | | -----| -------- | |op_CheckedAdditionAssignment| checked '+=' | |op_CheckedSubtractionAssignment| checked '-=' | |op_CheckedMultiplicationAssignment| checked '*=' | |op_CheckedDivisionAssignment| checked '/=' |

Präfix-Inkrementoperator und Präfix-Dekrementoperator

Siehe https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1296-prefix-increment-and-decrement-operators.

Wenn x in «op» x als Variable klassifiziert wird und eine neue Sprachversion gezielt ist, wird die Priorität den Instanzinkrementierungsoperatoren wie folgt zugewiesen.

Zunächst wird versucht, den Vorgang zu verarbeiten, indem instanzinkrementeller Operatorüberladungsauflösung angewendet wird. Wenn der Prozess kein Ergebnis und keinen Fehler erzeugt, wird der Vorgang verarbeitet, indem die unäre Operatorüberladungsauflösung angewendet wird, wie https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1296-prefix-increment-and-decrement-operators derzeit angegeben.

Andernfalls wird ein Vorgang «op»x wie folgt ausgewertet.

Wenn der Typ eines x Referenztyps bekannt ist, wird der x Ausgewertet, um eine Instanz x₀abzurufen, wird die Operatormethode für diese Instanz aufgerufen und x₀ als Ergebnis des Vorgangs zurückgegeben. Wenn x₀ ja null, löst der Aufruf der Operatormethode eine NullReferenceException aus.

Beispiel:

var a = ++(new C()); // error: not a variable
var b = ++a; // var temp = a; temp.op_Increment(); b = temp; 
++b; // b.op_Increment();
var d = ++C.P1; // error: setter is missing
++C.P1; // error: setter is missing
var e = ++C.P2; // var temp = C.op_Increment(C.get_P2()); C.set_P2(temp); e = temp;
++C.P2; // var temp = C.op_Increment(C.get_P2()); C.set_P2(temp);

class C
{
    public static C P1 { get; } = new C();
    public static C P2 { get; set; } = new C();

    public static C operator ++(C x) => ...;
    public void operator ++() => ...;
}

Wenn der Typ nicht x bekannt ist, dass es sich um einen Verweistyp handelt:

  • Wenn das Ergebnis einer Inkrementierung verwendet wird, wird die x Ausgewertet, um eine Instanz x₀abzurufen, wird die Operatormethode für diese Instanz aufgerufen, x₀ wird zugewiesen x und x₀ als Ergebnis der verbundzuordnung zurückgegeben.
  • Andernfalls wird die Operatormethode aufgerufen x.

Beachten Sie, dass Nebenwirkungen nur x einmal im Prozess ausgewertet werden.

Beispiel:

var a = ++(new S()); // error: not a variable
var b = ++S.P2; // var temp = S.op_Increment(S.get_P2()); S.set_P2(temp); b = temp;
++S.P2; // var temp = S.op_Increment(S.get_P2()); S.set_P2(temp);
++b; // b.op_Increment(); 
var d = ++S.P1; // error: set is missing
++S.P1; // error: set is missing
var e = ++b; // var temp = b; temp.op_Increment(); e = (b = temp); 

struct S
{
    public static S P1 { get; } = new S();
    public static S P2 { get; set; } = new S();

    public static S operator ++(S x) => ...;
    public void operator ++() => ...;
}

Postfix-Inkrementoperator und Postfix-Dekrementoperator

Siehe https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators.

Wenn das Ergebnis des Vorgangs verwendet wird oder nicht x als Variable klassifiziert wird oder x «op» eine alte Sprachversion gezielt ist, wird der Vorgang verarbeitet, indem die unäre Operatorüberladungsauflösung angewendet wird, wie https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators derzeit angegeben. Der Grund, warum wir nicht einmal versuchen, Instanzinkrementierungsoperatoren zu erhöhen, wenn das Ergebnis verwendet wird, ist die Tatsache, dass es, wenn wir mit einem Verweistyp arbeiten, nicht möglich ist, den Wert x vor dem Vorgang zu erzeugen, wenn er direkt mutiert wird. Wenn wir mit einem Werttyp arbeiten, müssen wir trotzdem Kopien erstellen usw.

Andernfalls wird die Priorität den Instanzinkrementierungsoperatoren wie folgt zugewiesen.

Zunächst wird versucht, den Vorgang zu verarbeiten, indem instanzinkrementeller Operatorüberladungsauflösung angewendet wird. Wenn der Prozess kein Ergebnis und keinen Fehler erzeugt, wird der Vorgang verarbeitet, indem die unäre Operatorüberladungsauflösung angewendet wird, wie https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators derzeit angegeben.

Andernfalls wird ein Vorgang x«op» wie folgt ausgewertet.

Wenn der Typ eines x Referenztyps bekannt ist, wird die Operatormethode aufgerufen x. Wenn x ja null, löst der Aufruf der Operatormethode eine NullReferenceException aus.

Beispiel:

var a = (new C())++; // error: not a variable
var b = new C(); 
var c = b++; // var temp = b; b = C.op_Increment(temp); c = temp; 
b++; // b.op_Increment();
var d = C.P1++; // error: missing setter
C.P1++; // error: missing setter
var e = C.P2++; // var temp = C.get_P2(); C.set_P2(C.op_Increment(temp)); e = temp;
C.P2++; // var temp = C.get_P2(); C.set_P2(C.op_Increment(temp));

class C
{
    public static C P1 { get; } = new C();
    public static C P2 { get; set; } = new C();

    public static C operator ++(C x) => ...; 
    public void operator ++() => ...;
}

Wenn der Typ nicht x als Verweistyp bekannt ist, wird die Operatormethode aufgerufen x.

Beispiel:

var a = (new S())++; // error: not a variable
var b = S.P2++; // var temp = S.get_P2(); S.set_P2(S.op_Increment(temp)); b = temp;
S.P2++; // var temp = S.get_P2(); S.set_P2(S.op_Increment(temp));
b++; // b.op_Increment(); 
var d = S.P1++; // error: set is missing
S.P1++; // error: missing setter
var e = b++; // var temp = b; b = S.op_Increment(temp); e = temp; 

struct S
{
    public static S P1 { get; } = new S();
    public static S P2 { get; set; } = new S();

    public static S operator ++(S x) => ...; 
    public void operator ++() => ...;
}

Instanzinkrementierungsoperatorüberladungsauflösung

Ein Vorgang des Formulars «op» x oder x «op», wobei «op» ein überladener Instanzinkrementoperator ist und x ein Ausdruck vom Typ Xist, wird wie folgt verarbeitet:

  • Die Gruppe der für den Vorgang X bereitgestellten operator «op»(x) benutzerdefinierten Kandidatenoperatoren wird mithilfe der Regeln der Kandidateninstanz-Inkrementoperatoren bestimmt.
  • Wenn das Set der in Frage kommenden benutzerdefinierten Operatoren nicht leer ist, wird dieses Set zum Set der in Frage kommenden Operatoren für die Operation. Andernfalls liefert die Überladungsauflösung kein Ergebnis.
  • Die Regeln für die Überladungsauflösung werden auf die Gruppe der Kandidatenoperatoren angewendet, um den besten Operator auszuwählen, und dieser Operator wird zum Ergebnis des Überladungsauflösungsprozesses. Wenn die Überladungsauflösung keinen einzigen, besten Operator wählen kann, tritt ein Bindungszeit-Fehler auf.

Inkrementierungsoperatoren für Kandidateninstanzen

Aufgrund eines Typs T und eines Vorgangs «op», bei dem «op» es sich um einen überladenden Instanzinkrementierer handelt, wird der Satz von kandidatenbenutzerdefinierten Operatoren T wie folgt bestimmt:

  • Im unchecked Evaluierungskontext handelt es sich um eine Gruppe von Operatoren, die vom Nachschlagevorgang des Mitglieds erzeugt werden, wenn nur Instanzoperatoren operator «op»() als übereinstimmend mit dem Zielnamen Nbetrachtet wurden.
  • Im checked Evaluierungskontext handelt es sich um eine Gruppe von Operatoren, die vom Member-Nachschlagevorgang erstellt werden, wenn nur Instanzen operator «op»() - und Instanzoperatoren operator checked «op»() als übereinstimmend mit dem Zielnamen Nbetrachtet wurden. Die operator «op»() Operatoren mit Deklarationen mit paarweisem Abgleich operator checked «op»() werden von der Gruppe ausgeschlossen.

Verbundzuweisung

Siehe https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12214-compound-assignment.

Der Absatz am Anfang, der dynamic behandelt wird, gilt weiterhin wie folgt.

Andernfalls wird die Priorität der zusammengesetzten Zuordnungsoperatorenx, wenn x «op»= y "in" als Variable klassifiziert wird und eine neue Sprachversion als Ziel verwendet wird.

Zunächst wird versucht, einen Vorgang des Formulars x «op»= y zu verarbeiten, indem die Überladung des Zusammengesetzten Zuordnungsoperators angewendet wird. Wenn der Prozess kein Ergebnis und keinen Fehler erzeugt, wird der Vorgang verarbeitet, indem die Binäre Operatorüberladungsauflösung angewendet wird, wie https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12214-compound-assignment derzeit angegeben.

Andernfalls wird der Vorgang wie folgt ausgewertet.

Wenn der Typ eines x Referenztyps bekannt ist, wird die x Methode ausgewertet, um eine Instanz x₀abzurufen, wird die Operatormethode für diese Instanz mit y dem Argument aufgerufen und x₀ als Ergebnis der zusammengesetzten Zuordnung zurückgegeben. Wenn x₀ ja null, löst der Aufruf der Operatormethode eine NullReferenceException aus.

Beispiel:

var a = (new C())+=10; // error: not a variable
var b = a += 100; // var temp = a; temp.op_AdditionAssignment(100); b = temp; 
var c = b + 1000; // c = C.op_Addition(b, 1000)
c += 5; // c.op_AdditionAssignment(5);
var d = C.P1 += 11; // error: setter is missing
var e = C.P2 += 12; // var temp = C.op_Addition(C.get_P2(), 12); C.set_P2(temp); e = temp;
C.P2 += 13; // var temp = C.op_Addition(C.get_P2(), 13); C.set_P2(temp);

class C
{
    public static C P1 { get; } = new C();
    public static C P2 { get; set; } = new C();

    // op_Addition
    public static C operator +(C x, int y) => ...;

    // op_AdditionAssignment
    public void operator +=(int y) => ...;
}

Wenn der Typ nicht x bekannt ist, dass es sich um einen Verweistyp handelt:

  • Wenn das Ergebnis einer zusammengesetzten Zuordnung verwendet wird, wird die x Methode ausgewertet, um eine Instanz x₀abzurufen, wird die Operatormethode für diese Instanz y mit dem Argument aufgerufen, x₀ wird zugewiesen x und x₀ als Ergebnis der verbundzuordnung zurückgegeben.
  • Andernfalls wird die Operatormethode als Argument aufgerufen xy .

Beachten Sie, dass Nebenwirkungen nur x einmal im Prozess ausgewertet werden.

Beispiel:

var a = (new S())+=10; // error: not a variable
var b = S.P2 += 100; // var temp = S.op_Addition(S.get_P2(), 100); S.set_P2(temp); b = temp;
S.P2 += 100; // var temp = S.op_Addition(S.get_P2(), 100); S.set_P2(temp);
var c = b + 1000; // c = S.op_Addition(b, 1000)
c += 5; // c.op_AdditionAssignment(5); 
var d = S.P1 += 11; // error: setter is missing
var e = c += 12; // var temp = c; temp.op_AdditionAssignment(12); e = (c = temp); 

struct S
{
    public static S P1 { get; } = new S();
    public static S P2 { get; set; } = new S();

    // op_Addition
    public static S operator +(S x, int y) => ...;

    // op_AdditionAssignment
    public void operator +=(int y) => ...;
}

Überladungsauflösung für zusammengesetzte Zuordnungsoperator

Ein Vorgang des Formulars x «op»= y, bei dem «op»= es sich um einen überladenden Verbundzuordnungsoperator handelt, x ist ein Ausdruck des Typs X , der wie folgt verarbeitet wird:

  • Der Satz von benutzerdefinierten Kandidatenoperatoren, die für den Vorgang X bereitgestellt werdenoperator «op»=(y), wird mithilfe der Regeln der Kandidatenverbundzuordnungsoperatoren bestimmt.
  • Wenn mindestens ein benutzerdefinierter Kandidatenoperator in der Gruppe auf die Argumentliste (y)anwendbar ist, wird dies zur Gruppe von Kandidatenoperatoren für den Vorgang. Andernfalls liefert die Überladungsauflösung kein Ergebnis.
  • Die Regeln für die Überladungsauflösung werden auf die Gruppe der Kandidatenoperatoren angewendet, um den besten Operator in Bezug auf die Argumentliste (y)auszuwählen, und dieser Operator wird zum Ergebnis des Überladungsauflösungsprozesses. Wenn die Überladungsauflösung keinen einzigen, besten Operator wählen kann, tritt ein Bindungszeit-Fehler auf.

Kandidatenverbundzuordnungsoperatoren

Aufgrund eines Typs T und eines Vorgangs «op»=, bei dem «op»= es sich um einen überladenden Verbundzuordnungsoperator handelt, wird der Satz von benutzerdefinierten Kandidatenoperatoren T wie folgt bestimmt:

  • Im unchecked Evaluierungskontext handelt es sich um eine Gruppe von Operatoren, die vom Nachschlagevorgang des Mitglieds erzeugt werden, wenn nur Instanzoperatoren operator «op»=(Y) als übereinstimmend mit dem Zielnamen Nbetrachtet wurden.
  • Im checked Evaluierungskontext handelt es sich um eine Gruppe von Operatoren, die vom Member-Nachschlagevorgang erstellt werden, wenn nur Instanzen operator «op»=(Y) - und Instanzoperatoren operator checked «op»=(Y) als übereinstimmend mit dem Zielnamen Nbetrachtet wurden. Die operator «op»=(Y) Operatoren mit Deklarationen mit paarweisem Abgleich operator checked «op»=(Y) werden von der Gruppe ausgeschlossen.

Offene Fragen

[Behoben] Sollte readonly modifizierer in Strukturen zulässig sein?

Es sieht so aus, als wäre es kein Vorteil, eine Methode zu kennzeichnen, readonly wenn der gesamte Zweck der Methode darin besteht, die Instanz zu ändern.

Schlussfolgerung: Wir werden readonly Modifizierer zulassen, aber wir werden die Zielanforderungen zu diesem Zeitpunkt nicht herabsetzen.

[Behoben] Sollte Schattenung zulässig sein?

Wenn eine abgeleitete Klasse einen Operator "zusammengesetzte Zuordnung"/"Instanz inkrementiert" mit derselben Signatur wie eine in der Basis deklariert, sollten wir einen override Modifizierer benötigen?

Schlussfolgerung: Schattieren wird mit den gleichen Regeln wie Methoden erlaubt sein.

[Behoben] Sollten wir eine Konsistenzsicherung zwischen deklarierten += und + Operatoren vornehmen?

Auf der LDM-2025-02-12 wurde die Sorge geäußert, dass Autoren ihre Benutzenden versehentlich in seltsame Szenarien drängen, in denen ein += vielleicht funktioniert, ein + aber nicht (oder umgekehrt), weil eine Form mehr Operatoren deklariert als die andere.

Schlussfolgerung: Es werden keine Überprüfungen zur Konsistenz zwischen verschiedenen Formen von Operatoren durchgeführt.

Alternativen

Beibehalten der Verwendung statischer Methoden

Wir könnten die Verwendung statischer Operatormethoden in Betracht ziehen, bei denen die zu mutierte Instanz als erster Parameter übergeben wird. Bei einem Werttyp muss dieser Parameter ein ref Parameter sein. Andernfalls kann die Methode die Zielvariable nicht stummschalten. Bei einem Klassentyp sollte dieser Parameter nicht gleichzeitig ein ref Parameter sein. Da bei einer Klasse die übergebene Instanz stummgeschaltet werden muss, nicht der Speicherort, an dem die Instanz gespeichert ist. Wenn ein Operator jedoch in einer Schnittstelle deklariert wird, ist es häufig nicht bekannt, ob die Schnittstelle nur von Klassen oder nur von Strukturen implementiert wird. Daher ist nicht klar, ob der erste Parameter ein ref Parameter sein soll.

Planungsbesprechungen