Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Megjegyzés:
Ez a cikk egy funkcióspecifikáció. A specifikáció a funkció tervezési dokumentumaként szolgál. Tartalmazza a specifikáció javasolt módosításait, valamint a funkció tervezése és fejlesztése során szükséges információkat. Ezeket a cikkeket mindaddig közzéteszik, amíg a javasolt specifikációmódosításokat nem véglegesítik, és be nem építik a jelenlegi ECMA-specifikációba.
A szolgáltatás specifikációja és a befejezett implementáció között eltérések lehetnek. Ezeket a különbségeket a vonatkozó nyelvi tervezési értekezlet (LDM) megjegyzései rögzítik.
A funkcióspektusok C# nyelvi szabványba való bevezetésének folyamatáról a specifikációkcímű cikkben olvashat bővebben.
A bajnokkal kapcsolatos kérdés: https://github.com/dotnet/csharplang/issues/9101
Összefoglalás
Lehetővé teszi, hogy a felhasználói típusok úgy szabják testre az összetett hozzárendelés-operátorok viselkedését, hogy a hozzárendelés célja helyben módosuljon.
Motiváció
A C# támogatja a fejlesztői túlterhelési operátor implementációit a felhasználó által definiált típushoz.
Emellett támogatja az "összetett hozzárendelési operátorokat", amelyek lehetővé teszik a felhasználó számára, hogy a kódot x += y
-hoz hasonlóan írja, ahelyett, hogy x = x + y
-et használna. A nyelv azonban jelenleg nem teszi lehetővé, hogy a fejlesztő túlterhelje ezeket az összetett hozzárendelési operátorokat, és bár az alapértelmezett viselkedés helyesen működik, különösen, mivel nem módosítható értéktípusokra vonatkozik, nem mindig "optimális".
Az alábbi példában
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();
}
az aktuális nyelvi szabályokkal az összetett hozzárendelési operátor c1 += 1
meghívja a felhasználó által definiált +
operátort, majd hozzárendeli annak visszatérési értékét a helyi változóhoz c1
. Vegye figyelembe, hogy az operátor implementációjának ki kell osztania C1
és vissza kell adnia egy új példányt, míg a fogyasztó szemszögéből az eredeti példányra C1
való helyben történő módosítás éppoly jól működne (a hozzárendelés után nem használják), további előnye pedig az lenne, hogy elkerülhető egy extra kiosztás.
Ha egy program összetett hozzárendelési műveletet használ, a leggyakoribb hatás az, hogy az eredeti érték "elveszett", és már nem érhető el a program számára. A nagy adatokkal (például BigInteger, Tensors stb.) rendelkező típusok esetében a nettó új célhely létrehozásának, az iterálásnak és a memória másolásának költsége általában meglehetősen költséges. A helyszíni mutáció sok esetben lehetővé tenné a költségek kihagyását, ami jelentős javulást jelenthet az ilyen forgatókönyvekben.
Ezért hasznos lehet, ha a C# lehetővé teszi, hogy a felhasználói típusok testre szabják az összetett hozzárendelési operátorok viselkedését, és optimalizálják azokat a forgatókönyveket, amelyek egyébként erőforrásokat igényelnének és másolást igényelnének.
Részletes kialakítás
Szemantika
https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15101-general A nyelvtan a következőképpen van módosítva.
Az operátorok operator_declarationhasználatával vannak deklarálva:
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 ';'
| ';'
;
A túlterhelhető operátorok öt kategóriája létezik: nem kötelező operátorok, bináris operátorok, konverziós operátorok, növekményes operátorok, összetett hozzárendelési operátorok.
Az alábbi szabályok az összes operátor-deklarációra vonatkoznak:
- Az üzemeltetői
nyilatkozatnaktartalmaznia kell egypublic
és egymódosító is.static
Az összetett hozzárendelési és példánynövekmény-operátorok elrejthetik az alaposztályban deklarált operátorokat. Ezért a következő bekezdés már nem pontos, és vagy ennek megfelelően kell módosítani, vagy eltávolítható:
Mivel az operátor-deklarációk mindig megkövetelik azt az osztályt vagy utasítást, amelyben az operátor az operátor aláírásában való részvételre van deklarálva, nem lehetséges, hogy egy származtatott osztályban deklarált operátor elrejtse az alaposztályban deklarált operátort. Így a
new
módosító soha nem szükséges, és ezért soha nem engedélyezett egy operátori nyilatkozatban.
Unary operátorok
Lásd a(z) https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15102-unary-operators.
Az üzemeltetői nyilatkozatnak tartalmaznia kell egy static
módosítót, és nem tartalmazhat módosítót override
.
A következő listajelet eltávolítják:
- A nem jegyzőnek
++
vagy--
operátornak egyetlen típusparamétertT
kell alkalmaznia, vagyT?
ugyanazt a típust vagy az abból származtatott típust kell visszaadni.
A következő bekezdés úgy van igazítva, hogy már nem említi a ++
és --
operátor tokeneket.
A nem kötelező operátorok aláírása az operátori jogkivonatból (
+
,-
,!
,~
,++
, ,--
, , vagytrue
false
) és az egyetlen paraméter típusából áll. A visszatérési típus nem része egy nem kötelező operátor aláírásának, és nem is a paraméter neve.
A szakasz egy példáját úgy kell módosítani, hogy ne használjon felhasználó által definiált növekményes operátort.
Bináris operátorok
Lásd a(z) https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15103-binary-operators.
Az üzemeltetői nyilatkozatnak tartalmaznia kell egy static
módosítót, és nem tartalmazhat módosítót override
.
Konverziós operátorok
Az üzemeltetői nyilatkozatnak tartalmaznia kell egy static
módosítót, és nem tartalmazhat módosítót override
.
Növekményes operátorok
A statikus növekményes operátor-deklarációkra az alábbi szabályok vonatkoznak, amelyek T
az operátordeklarációt tartalmazó osztály vagy szerkezet példánytípusát jelölik:
- Az üzemeltetői nyilatkozatnak tartalmaznia kell egy
static
módosítót, és nem tartalmazhat módosítótoverride
. - Az operátornak egyetlen
T
vagyT?
típusú paramétert kell felvennie, és ugyanolyan típusú vagy abból származtatott típust kell visszaadnia.
A statikus növekményes operátor szignatúrája az operátor szimbólumaiból ('ellenőrzött'? ++
, 'ellenőrzött'? --
) és az egyetlen paraméter típusából áll.
A visszatérési típus nem része egy statikus növekményes operátor aláírásának, és nem is a paraméter neve.
A statikus növekményes operátorok nagyon hasonlítanak a nem állandó operátorokhoz.
Az alábbi szabályok vonatkoznak a példány növelő operátor deklarációira:
- Az üzemeltetői nyilatkozat nem tartalmazhat módosítót
static
. - Az üzemeltetőnek nem kell paramétereket figyelembe vennie.
- Az üzemeltetőnek visszatérési típussal kell rendelkeznie
void
.
A példány-növekményes operátor tulajdonképpen egy üres, paraméter nélküli, a metaadatokban speciális névvel rendelkező példánymetódus.
A példánynövekmény-operátor szignatúrája az operátori tokeneket tartalmazza ('ellenőrizve'? '++' | 'ellenőrizve'? '--').
Egy checked operator
deklarációhoz egy regular operator
páros deklaráció szükséges. Máskülönben fordítási időhiba lép fel.
Lásd még: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/checked-user-defined-operators.md#semantics.
A módszer célja, hogy a példány értékét a kért növekményes művelet eredményéhez igazítsa, függetlenül attól, hogy ez mit jelent a deklarálási típus kontextusában.
Példa:
class C1
{
public int Value;
public void operator ++()
{
Value++;
}
}
A példánynövekmény-operátor felülbírálhatja az alaposztályban deklarált ugyanazzal az aláírással rendelkező operátorokat override
, és erre a célra egy módosító használható.
A inkrementálás/dekrementálás operátorok példányváltozatainak támogatásához a következő "fenntartott" speciális neveket kell hozzáadni az ECMA-335-höz: | Név | Operátor | | -----| -------- | |op_DecrementAssignment| --
| |op_IncrementAssignment| ++
| |op_CheckedDecrementAssignment| ellenőrzött --
| |op_CheckedIncrementAssignment| ellenőrzött ++
|
Összetett hozzárendelési operátorok
Az összetett hozzárendelési operátorok deklarációira a következő szabályok vonatkoznak:
- Az üzemeltetői nyilatkozat nem tartalmazhat módosítót
static
. - Az operátornak egy paramétert kell alkalmaznia.
- Az üzemeltetőnek visszatérési típussal kell rendelkeznie
void
.
Az összetett hozzárendelési operátor tulajdonképpen egy üres, visszaadott példánymetódus, amely egy paramétert vesz igénybe, és a metaadatokban speciális névvel rendelkezik.
Az összetett hozzárendelési operátor szignatúrája az operátor-szekvenciákból ('ellenőrzött'? '+=', 'ellenőrzött'? '-=', 'ellenőrzött'? '*=', 'ellenőrzött'? '/=', '%=', '&=', '|=', '^=', '='<<, jobbra_shift_hozzárendelés, előjel_nélküli_jobbra_shift_hozzárendelés) és az egyetlen paraméter típusából áll. A paraméter neve nem része egy összetett hozzárendelési operátor aláírásának.
Egy checked operator
deklarációhoz egy regular operator
páros deklaráció szükséges. Máskülönben fordítási időhiba lép fel.
Lásd még: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/checked-user-defined-operators.md#semantics.
A módszer célja, hogy a példány értékét a <instance> <binary operator token> parameter
eredményéhez igazítsa.
Példa:
class C1
{
public int Value;
public void operator +=(int x)
{
Value+=x;
}
}
Az összetett hozzárendelési operátorok felülbírálhatják az alaposztályban deklarált azonos aláírással rendelkező operátorokat override
, erre a célra egy módosító használható.
Az ECMA-335 már "fenntartotta" a következő speciális neveket a felhasználó által definiált növekményes operátorokhoz: | Név | Operátor | | -----| -------- | |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|
Azonban azt állítja, hogy a CLS-megfelelőség megköveteli, hogy az operátori metódusok ne legyenek üres statikus metódusok két paraméterrel, azaz megegyeznek a C# bináris operátorokkal. Érdemes megfontolnunk a CLS-megfelelőségi követelmények lazítását, hogy az operátorok egyetlen paraméterrel visszaadott példánymetódusokat semmissé tegyenek.
Az operátorok ellenőrzött verzióinak támogatásához a következő neveket kell hozzáadni: | Név | Operátor | | -----| -------- | |op_CheckedAdditionAssignment| ellenőrzött "+=" | |op_CheckedSubtractionAssignment| ellenőrzött "-=" | |op_CheckedMultiplicationAssignment| ellenőrzött "*=" | |op_CheckedDivisionAssignment| ellenőrzött "/=" |
Előtag növelő és csökkentő operátorok
Ha x
változóként van osztályozva, és egy új nyelvi verzió van megcélozva, akkor a «op» x
prioritása van az alábbiak szerint.
Először megpróbáljuk a műveletet feldolgozni a példánynövekmény operátor túlterhelésének feloldásával. Ha a folyamat nem hoz létre eredményt és nem eredményez hibát, akkor a művelet az https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1296-prefix-increment-and-decrement-operators által jelenleg meghatározott egynemű operátor túlterhelés feloldásával történik.
Ellenkező esetben a rendszer a következőképpen értékeli ki a műveletet «op»x
.
Ha a x
ismert, hogy referenciatípus, akkor a x
kiértékelésre kerül, hogy előálljon egy x₀
példány. Ezen a példányon meghívják az operátormetódust, és a x₀
visszaadásra kerül mint a művelet eredménye.
Ha x₀
null
, az operátor metódus meghívása NullReferenceException kivételt vált ki.
Például:
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 ++() => ...;
}
Ha a x
típusa nem ismert referenciatípusként:
- Ha a növekmény eredményét használják, a
x
kiértékeli egy példány megszerzésétx₀
, majd az operátor metódust meghívja az adott példányon,x₀
hozzá van rendelvex
-hoz, ésx₀
az összetett hozzárendelés eredményeként visszaadódik. - Ellenkező esetben az operátor metódusa meghívódik a(z)
x
elemre.
Vegye figyelembe, hogy a x
mellékhatások csak egyszer vannak kiértékelve a folyamat során.
Például:
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 növekmény- és csökkentés operátorok
Ha a művelet eredménye felhasználásra kerül, vagy x
x «op»
nem sorolható be változóként, vagy egy régi nyelvi verzió a cél, akkor a művelet feldolgozása a jelenleg https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators által meghatározott módon, az unáris operátor túlterhelések feloldásával történik.
Azért nem próbáljuk meg az eredmény felhasználásakor instance növény operátorokat alkalmazni, mert ha referenciatípussal van dolgunk, a művelet előtt nem lehet x
értéket előállítani, ha az helyben mutálódik.
Ha egy értéktípussal foglalkozunk, akkor is másolatokat kell készítenünk stb.
Különben a példány növekményes operátorai az alábbiak szerint élveznek elsőbbséget.
Először megpróbáljuk a műveletet feldolgozni a példánynövekmény operátor túlterhelésének feloldásával. Ha a folyamat nem hoz létre eredményt és nem eredményez hibát, akkor a művelet az https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators által jelenleg meghatározott egynemű operátor túlterhelés feloldásával történik.
Ellenkező esetben a rendszer a következőképpen értékeli ki a műveletet x«op»
.
Ha a x
típusáról ismert, hogy referenciatípus, az operátor metódus a x
-en kerül meghívásra.
Ha x
null
, az operátor metódus meghívása NullReferenceException kivételt vált ki.
Például:
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 ++() => ...;
}
Ha a x
típus nem ismert referenciatípusként, az operátor metódusát meghívják a x
-en.
Például:
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 ++() => ...;
}
Példány növekményes operátor túlterhelésének feloldása
Az «op» x
vagy x «op»
formájú művelet – ahol az «op» egy túlterhelhető példány inkrementáló operátor – és x
egy X
típusú kifejezés, a következőképpen lesz feldolgozva:
- A
X
által aoperator «op»(x)
művelethez megadott jelölt felhasználó által definiált operátorok készlete a jelölt példány növekményes operátorainak szabályait követve van meghatározva. - Ha a felhasználó által definiált jelölt operátorok készlete nem üres, akkor ez lesz a művelet jelölt operátorainak halmaza. Ellenkező esetben a túlterhelés feloldása nem eredményez eredményt.
- A túlterhelésfeloldási szabályok a jelölt operátorokra vonatkoznak a legjobb operátor kiválasztásához, és ez az operátor lesz a túlterhelésfeloldási folyamat eredménye. Ha a túlterhelés feloldása nem tud egyetlen legjobb operátort kiválasztani, bindolási idő hiba érkezik létre.
A jelölt példányok növekményes operátorai
Egy típus T
és egy művelet «op»
– ahol «op»
egy túlterhelhető példány növekményes operátor – alapján a felhasználó által T
megadott jelölt operátorok készlete az alábbiak szerint van meghatározva:
- A
unchecked
kiértékelési kontextusban ez az operátorok egy olyan csoportja, amelyet a tag-keresési folyamat állít elő, amikor csak azokat azoperator «op»()
példányoperátorokat veszik figyelembe, amelyek megfelelnek aN
célnévnek. - A
checked
kiértékelési kontextusban az operátorok azon csoportjáról van szó, amelyet a tagkeresési folyamat hoz létre, amikor csak a példányoperator «op»()
és példányoperator checked «op»()
operátorokat tekintik úgy, hogy megfelelnek a célnévnekN
. Aoperator «op»()
operátorok, amelyek egymással párosan egyezőoperator checked «op»()
deklarációkkal rendelkeznek, ki vannak zárva a csoportból.
Összetett hozzárendelés
Az első bekezdés, amellyel dynamic
foglalkozik, továbbra is érvényes.
Ellenkező esetben, ha a x
x «op»= y
változóként van besorolva, és egy új nyelvi verzió a cél, akkor az alábbiak szerint az összetett hozzárendelési operátorok prioritást élveznek.
Először kísérletet tesz az űrlap x «op»= y
egy műveletének feldolgozására összetett hozzárendelési operátor túlterhelésének feloldásával.
Ha a folyamat nem hoz létre eredményt és nem ad hibát, akkor a folyamat a bináris operátorok túlterhelésének feloldását https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12214-compound-assignment alkalmazza az aktuálisan megadott módon.
Ellenkező esetben a rendszer a következőképpen értékeli ki a műveletet.
Ha a x
típusa ismerten referenciatípus, akkor a x
-et kiértékeljük, hogy egy x₀
példányt kapjunk, majd az operátor metódusát meghívjuk azon a példányon y
argumentumként, és a x₀
az összetett hozzárendelés eredményeként kerül visszaadásra.
Ha x₀
null
, az operátor metódus meghívása NullReferenceException kivételt vált ki.
Például:
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) => ...;
}
Ha a x
típusa nem ismert referenciatípusként:
- Ha az összevont hozzárendelés eredményét használják, akkor a
x
kiértékelésre kerül egyx₀
példány lekéréséhez, az operátormódszer meghívásra kerül az adott példányon ay
argumentummal, ezt követően ax₀
hozzá van rendelve ax
-hez, és ax₀
visszaadódik az összevont hozzárendelés eredményeként. - Ellenkező esetben az
x
lesz alkalmazva argumentumként azy
operátor függvényére.
Vegye figyelembe, hogy a x
mellékhatások csak egyszer vannak kiértékelve a folyamat során.
Például:
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) => ...;
}
Összetett hozzárendelés operátor túlterhelésének feloldása
A x «op»= y
alakú művelet – ahol «op»=
egy túlterhelhető összetett hozzárendelési operátor, x
pedig X
típusú kifejezés – a következőképpen lesz feldolgozva:
- A
X
által aoperator «op»=(y)
művelethez megadott jelölt operátorok halmazát a jelölt összetett hozzárendelési operátorok szabályai határozzák meg. - Ha a halmaz legalább egy felhasználó által definiált jelölt operátora alkalmazható az argumentumlistára
(y)
, akkor ez lesz a művelethez tartozó jelölt operátorok halmaza. Ellenkező esetben a túlterhelés feloldása nem eredményez eredményt. - A túlterhelésfeloldási szabályokat alkalmazzuk a jelölt operátorok halmazára, hogy kiválasztja a legjobb operátort az argumentumlista alapján, és ez az operátor válik a túlterhelésfeloldási folyamat eredményévé. Ha a túlterhelés feloldása nem tud egyetlen legjobb operátort kiválasztani, bindolási idő hiba érkezik létre.
Lehetséges összetett hozzárendelési operátorok
Egy típus T
és egy művelet «op»=
– ahol «op»=
egy túlterhelhető összetett hozzárendelési operátor – alapján a felhasználó által T
megadott jelölt operátorok készlete az alábbiak szerint van meghatározva:
- A
unchecked
kiértékelési kontextusban ez az operátorok egy olyan csoportja, amelyet a tag-keresési folyamat állít elő, amikor csak azokat azoperator «op»=(Y)
példányoperátorokat veszik figyelembe, amelyek megfelelnek aN
célnévnek. - A
checked
kiértékelési kontextusban az operátorok azon csoportjáról van szó, amelyet a tagkeresési folyamat hoz létre, amikor csak a példányoperator «op»=(Y)
és példányoperator checked «op»=(Y)
operátorokat tekintik úgy, hogy megfelelnek a célnévnekN
. Aoperator «op»=(Y)
operátorok, amelyek egymással párosan egyezőoperator checked «op»=(Y)
deklarációkkal rendelkeznek, ki vannak zárva a csoportból.
Kérdések megnyitása
[Megoldott] Engedélyezve kell readonly
lennie a módosítónak a struktúrákban?
Úgy érzi, hogy nem lenne előnyös, ha lehetővé tenné egy metódus readonly
megjelölését, ha a metódus teljes célja a példány módosítása.
Következtetés: Engedélyezzük a readonly
módosítókat, de jelenleg nem ellazítjuk a célkövetelményeket.
[Megoldott] Engedélyezni kell az árnyékolást?
Ha egy származtatott osztály "összetett hozzárendelés"/"példánynövekmény" operátort deklarál, ugyanazzal a szignatúrával, mint az alap osztályban lévővel, akkor szükség van-e override
módosítóra?
Következtetés: Az árnyékolás ugyanazokkal a szabályokkal engedélyezett, mint a metódusok.
[Megoldott] Szükség van-e konzisztenciára a deklarált operátorok között +=
és +
?
Az LDM-2025-02-12 során aggály merült fel azzal kapcsolatban, hogy a szerzők véletlenül olyan furcsa forgatókönyvekbe küldik a felhasználókat, ahol +=
egy lehetséges megoldás működik, de +
nem (vagy fordítva), mert az egyik űrlap további operátorokat deklarál, mint a másik.
Következtetés: A különböző operátorok közötti konzisztencia ellenőrzése nem történik meg.
Alternatívák
Továbbra is használja a statikus metódusokat
Megfontolhatnánk statikus operátori metódusok használatát, ahol a mutálandó példányt az első paraméterként adjuk át. Értéktípus esetén ennek a paraméternek paraméternek ref
kell lennie.
Ellenkező esetben a metódus nem tudja mutálni a célváltozót. Ugyanakkor osztálytípus esetén ez a paraméter nem lehet ref
paraméter. Mivel egy osztály esetében az átadott példányt módosítani kell, és nem a példány tárolási helyét. Ha azonban egy operátort egy felületen deklarálnak, gyakran nem lehet tudni, hogy az interfészt csak osztályok vagy csak struktúrák implementálják-e. Ezért nem egyértelmű, hogy az első paraméter ref
paraméter kell-e legyen.
Tervezői értekezletek
C# feature specifications