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.
Jegyzet
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.
Bajnoki probléma: https://github.com/dotnet/csharplang/issues/2691
Összefoglalás
Az osztályok és a szerkezetek paraméterlistával rendelkezhetnek, és az alaposztály-specifikációjuk argumentumlistával rendelkezhet. Az elsődleges konstruktorparaméterek hatókörben vannak az osztály vagy a szerkezet deklarációja során, és ha egy függvénytag vagy egy névtelen függvény rögzíti őket, megfelelően vannak tárolva (például a deklarált osztály vagy szerkezet kimondhatatlan privát mezőiként).
A javaslat "újraértelmezi" az elsődleges konstruktorokat, amelyek már elérhetőek a rekordokon, a funkció általánosabb jellegének megfelelően, további tagok hozzáadásával.
Motiváció
A C# osztályának vagy szerkezetének lehetősége, hogy több konstruktorsal is rendelkezik, általánosságot biztosít, de a deklaráció szintaxisában szereplő néhány tedium rovására, mivel a konstruktor bemenetét és az osztály állapotát tisztán el kell különíteni.
Az elsődleges konstruktorok egy konstruktor paramétereit az inicializáláshoz vagy közvetlenül objektumállapotként használni kívánt egész osztály vagy szerkezet hatókörébe helyezik. A kompromisszum az, hogy minden más konstruktornak az elsődleges konstruktort kell hívnia.
public class B(bool b) { } // base class
public class C(bool b, int i, string s) : B(b) // b passed to base constructor
{
public int I { get; set; } = i; // i used for initialization
public string S // s used directly in function members
{
get => s;
set => s = value ?? throw new ArgumentNullException(nameof(S));
}
public C(string s) : this(true, 0, s) { } // must call this(...)
}
Részletes kialakítás
Ez ismerteti a rekordok és a nem rekordok általánosított kialakítását, majd részletezi a rekordok meglévő elsődleges konstruktorainak megadását úgy, hogy szintetizált tagokat ad hozzá egy elsődleges konstruktor jelenlétében.
Szintaxis
Az osztály- és szerkezetdeklarációk ki vannak bővítve, hogy lehetővé tegyék a típusnév paraméterlistáját, az alaposztály argumentumlistáját, valamint egy csak egy ;álló törzset:
class_declaration
: attributes? class_modifier* 'partial'? class_designator identifier type_parameter_list?
parameter_list? class_base? type_parameter_constraints_clause* class_body
;
class_designator
: 'record' 'class'?
| 'class'
class_base
: ':' class_type argument_list?
| ':' interface_type_list
| ':' class_type argument_list? ',' interface_type_list
;
class_body
: '{' class_member_declaration* '}' ';'?
| ';'
;
struct_declaration
: attributes? struct_modifier* 'partial'? 'record'? 'struct' identifier type_parameter_list?
parameter_list? struct_interfaces? type_parameter_constraints_clause* struct_body
;
struct_body
: '{' struct_member_declaration* '}' ';'?
| ';'
;
interface_declaration
: attributes? interface_modifier* 'partial'? 'interface'
identifier variant_type_parameter_list? interface_base?
type_parameter_constraints_clause* interface_body
;
interface_body
: '{' interface_member_declaration* '}' ';'?
| ';'
;
enum_declaration
: attributes? enum_modifier* 'enum' identifier enum_base? enum_body
;
enum_body
: '{' enum_member_declarations? '}' ';'?
| '{' enum_member_declarations ',' '}' ';'?
| ';'
;
Megjegyzés: Ezek a produkciók felváltják a record_declaration a Rekordokban és a record_struct_declaration a Rekordszerkezetekben, amelyek mindkettő elavulttá válnak.
Az hiba, ha egy class_base rendelkezik argument_list-vel, miközben a körülvevő class_declaration nem tartalmaz parameter_list-at. Egy részleges osztály vagy szerkezet legfeljebb egy olyan részleges típus deklaráció lehet, amely tartalmazhatja a parameter_list. A parameter_list deklaráció record paramétereknek mind értékparamétereknek kell lenniük.
Vegye figyelembe, hogy e javaslat szerint a class_body, struct_body, interface_body és enum_body csak egy ;-ből állhatnak.
Egy parameter_list rendelkező osztály vagy szerkezet implicit nyilvános konstruktorsal rendelkezik, amelynek aláírása megfelel a típusdeklaráció értékparamétereinek. Ez az elsődleges konstruktor a típushoz, és az implicit módon deklarált paraméter nélküli konstruktort, ha van, letiltja. Hiba, ha egy elsődleges konstruktor és egy ugyanolyan aláírású konstruktor már szerepel a típusdeklarációban.
Keresés
Az egyszerű nevek keresése ki van bővítve az elsődleges konstruktorparaméterek kezeléséhez. A változtatások félkövérrel kiemelve az alábbi részletben láthatók:
- Ellenkező esetben minden példánytípus esetében
T(§15.3.2), kezdve az azonnal belefoglalt típusdeklaráció példánytípusával, és folytatva az egyes belefoglaló osztályok vagy szerkezetdeklarációk példánytípusát (ha van):
- Ha a
Tdeklarációja tartalmaz egy elsődleges konstruktorparamétertI, és a hivatkozásargument_listTclass_base-ján belül történik, vagyTbármely mező, tulajdonság vagy esemény inicializálójában, akkor az eredmény az elsődleges konstruktorparaméterI.- Ellenkező esetben, ha
enulla, és aTdeklarációja tartalmaz egyInevű típusparamétert, akkor a simple_name erre a típusparaméterre hivatkozik.- Ellenkező esetben, ha a taghoz való keresés a
Ialapján aTetípusú argumentumokkal megegyezést hoz létre:
- Ha
Taz azonnal belefoglalt osztály vagy struktúratípus példánytípusa, és a keresés egy vagy több metódust azonosít, az eredmény egy metóduscsoport, amely athistársított példánykifejezésével rendelkezik. Ha típusargumentumlistát adott meg, az általános metódus meghívására szolgál (§12.8.10.2).- Ellenkező esetben, ha a
Taz azonnal belefoglalt osztály vagy struktúratípus példánytípusa, ha a keresés azonosít egy példánytagot, és ha a hivatkozás egy példánykonstruktor, példánymetódus vagy példánykiegészítő blokkban történik (§12.2.1), az eredmény megegyezik az űrlap taghozzáférésével (this.I. Ez csak akkor fordulhat elő, haenulla.- Ellenkező esetben az eredmény megegyezik az űrlap vagy
T.Itaghozzáférésével (T.I<A₁, ..., Aₑ>).- Ellenkező esetben, ha a
Tdeklarációja tartalmaz egy elsődleges konstruktorparamétertI, az eredmény az elsődleges konstruktorparaméterI.
Az első hozzáadás megfelel az elsődleges konstruktorok általrekordokon bekövetkezett változásnak, és biztosítja, hogy az elsődleges konstruktorparaméterek az inicializálók és az alaposztály argumentumainak megfelelő mezők előtt legyenek megtalálhatók. Ezt a szabályt a statikus inicializálókra is kiterjeszti. Azonban mivel a rekordoknak mindig van egy példánytagjuk, amelynek ugyanaz a neve, mint a paraméternek, a bővítés legfeljebb egy hibaüzenet megváltozásához vezethet. A paraméterhez való illegális hozzáférés és a példánytaghoz való illegális hozzáférés.
A második kiegészítés lehetővé teszi, hogy az elsődleges konstruktor paraméterek a típustörzs más részein találhatók meg, de csak akkor, ha a tagok nincsenek takarásban.
Hiba az elsődleges konstruktorparaméterre hivatkozni, ha a hivatkozás nem az alábbiak egyikén belül fordul elő:
- egy
nameofargumentum - a deklaráló típus egy példánymezőjének, tulajdonságának vagy eseményének inicializálója (a típus deklaráló alapértelmezett konstruktor a paraméterrel)
- a
argument_lista deklarálási típusclass_base-je. - a deklarálási típushoz tartozó példánymetódus törzse (vegye figyelembe, hogy a példánykonstruktorok nem tartoznak ide).
- a deklarálási típushoz tartozó példány tartozékának törzse.
Más szóval az elsődleges konstruktorparaméterek hatókörben vannak a deklarálási típus törzsében. Mező, tulajdonság vagy esemény inicializálójában árnyékot vetnek a deklaráló típus tagjaira, vagy a deklaráló típus argument_listclass_base-jában. A deklarálási típus tagjai minden máshol árnyékba kerülnek.
Így a következő deklarációban:
class C(int i)
{
protected int i = i; // references parameter
public int I => i; // references field
}
A mező inicializálója i a iparaméterre hivatkozik, míg a tulajdonság törzse I a mezőre i.
Figyelmeztetés az árnyékolásra egy tag által a bázisról
A Fordító figyelmeztetést küld az azonosítók használatára vonatkozóan, ha egy alaptag árnyékolással árnyékel egy elsődleges konstruktorparamétert, ha az elsődleges konstruktorparamétert nem adták át az alaptípusnak a konstruktoron keresztül.
Az elsődleges konstruktorparaméter akkor tekinthető át az alaptípusnak a konstruktoron keresztül, ha az alábbi feltételek teljesülnek az class_baseargumentumában:
- Az argumentum egy elsődleges konstruktorparaméter implicit vagy explicit identitásátalakítását jelenti;
- Az argumentum nem része kibontott
paramsargumentumnak;
Szemantika
Az elsődleges konstruktor egy példánykonstruktor generálását eredményezi a záró típuson a megadott paraméterekkel. Ha a class_base argumentumlistával rendelkezik, a létrehozott példánykonstruktor base inicializálóval rendelkezik ugyanazzal az argumentumlistával.
Az osztály-/szerkezetdeklarációk elsődleges konstruktorparaméterei deklarálhatók ref, in vagy out. A ref vagy out paraméterek deklarálása továbbra is illegális marad a rekorddeklaráció elsődleges konstruktoraiban.
Az osztály törzsének összes példánytag-inicializálója hozzárendeléssé válik a létrehozott konstruktorban.
Ha egy elsődleges konstruktorparaméterre egy példánytagból hivatkozik, és a hivatkozás nem egy nameof argumentumon belül van, a rendszer rögzíti a beágyazási típus állapotába, hogy a konstruktor leállítása után is elérhető maradjon. A valószínű megvalósítási stratégia egy privát mezőn keresztül történik, amely egy módosított nevet használ. Egy írásvédett struktúrában a rögzítési mezők írásvédettek lesznek. Ezért az írásvédett struktúra lekötött paramétereihez való hozzáférés hasonló korlátozásokkal fog járni, mint az írásvédett mezőkhöz való hozzáférés. A rögzített paraméterekhez való hozzáférés egy olvasható tagon belül hasonló korlátozásokkal fog rendelkezni, mint a példánymezők hozzáférése ugyanabban a környezetben.
A ref-szerű paraméterek rögzítése nem engedélyezett, és a rögzítés nem engedélyezett ref, in vagy out paraméterek esetében. Ez hasonló a rögzítésnél a lambdákban alkalmazott korlátozáshoz.
Ha egy elsődleges konstruktorparaméterre csak a példánytag inicializálói hivatkoznak, azok közvetlenül hivatkozhatnak a generált konstruktor paraméterére, mivel azok végrehajtása annak részeként történik.
Az elsődleges konstruktor a következő műveletek sorozatát hajtja végre:
- A paraméterértékek a rögzítési mezőkben vannak tárolva, ha vannak ilyenek.
- A példány-inicializálók végrehajtásra kerülnek
- Az alapkonstruktor inicializálása megtörténik
Minden felhasználói kód paraméterhivatkozásait a rendszer a megfelelő rögzítési mezőhivatkozásokra cseréli.
Például ez a deklaráció:
public class C(bool b, int i, string s) : B(b) // b passed to base constructor
{
public int I { get; set; } = i; // i used for initialization
public string S // s used directly in function members
{
get => s;
set => s = value ?? throw new ArgumentNullException(nameof(value));
}
public C(string s) : this(true, 0, s) { } // must call this(...)
}
A következőhöz hasonló kódot hoz létre:
public class C : B
{
public int I { get; set; }
public string S
{
get => __s;
set => __s = value ?? throw new ArgumentNullException(nameof(value));
}
public C(string s) : this(0, s) { ... } // must call this(...)
// generated members
private string __s; // for capture of s
public C(bool b, int i, string s)
{
__s = s; // capture s
I = i; // run I's initializer
B(b) // run B's constructor
}
}
Hiba, hogy egy nem elsődleges konstruktor-deklaráció ugyanazt a paraméterlistát tartalmazza, mint az elsődleges konstruktor. Minden nem elsődleges konstruktor-deklarációnak this inicializálót kell használnia, hogy az elsődleges konstruktort végül meghívja.
A rekordok figyelmeztetést adnak, ha egy elsődleges konstruktorparaméter nem olvasható a (esetleg generált) példány-inicializálókban vagy az alap inicializálóban. Hasonló figyelmeztetések jelennek meg az osztályok és struktúrák elsődleges konstruktorparaméterei esetében:
- by-value paraméter esetén, ha a paraméter nincs rögzítve, és nem olvasható egyetlen példány inicializálóban vagy alap inicializálóban sem.
-
inparaméter esetében, ha a paraméter nem olvasható egyetlen példány-inicializálóban vagy alap inicializálóban sem. - egy
refparaméter esetében, ha a paraméter nem olvasható vagy nem írható meg egyetlen példány inicializálón vagy alap inicializálón belül sem.
Azonos egyszerű nevek és típusnevek
Létezik egy speciális nyelvi szabály az úgynevezett Színszín forgatókönyvekhez: Azonos egyszerű nevek és típusnevek.
Az
E.Iformájú taghozzáférés esetén, haEegyetlen azonosító, és ha aEjelentése simple_name (§12.8.4) egy olyan állandó, mező, tulajdonság, helyi változó vagy paraméter, amely azonos típusú, mint aEjelentése type_name (§7.8.1), akkor aEmindkét lehetséges jelentése engedélyezett. AE.Itagkeresése soha nem egyértelmű, mivelImindkét esetben feltétlenül azEtípusú tagnak kell lennie. Más szóval a szabály egyszerűen engedélyezi a hozzáférést a statikus tagokhoz és a beágyazottEtípusokhoz, ahol egyébként fordítási időhiba történt volna.
Az elsődleges konstruktorok tekintetében a szabály azt határozza meg, hogy egy példánytagon belüli azonosítót típushivatkozásként vagy elsődleges konstruktorparaméter-referenciaként kell-e kezelni, amely viszont a paramétert a beágyazási típus állapotába rögzíti. Annak ellenére, hogy "a E.I tagkeresése soha nem egyértelmű", ha a keresés egy tagcsoportot eredményez, bizonyos esetekben lehetetlen megállapítani, hogy a taghozzáférés statikus tagra vagy példánytagra vonatkozik-e anélkül, hogy teljes mértékben feloldanák (kötéssel) a taghozzáférést. Az elsődleges konstruktorparaméter rögzítése ugyanakkor úgy módosítja a beágyazási típus tulajdonságait, hogy az hatással van a szemantikai elemzésre. Előfordulhat például, hogy a típus nem lesz kezelve, és emiatt bizonyos korlátozások meghiúsulhatnak.
Vannak olyan forgatókönyvek is, amelyek esetében a kötés mindkét esetben sikeres lehet, attól függően, hogy a paraméter rögzítettnek minősül-e. Például:
struct S1(Color Color)
{
public void Test()
{
Color.M1(this); // Error: ambiguity between parameter and typename
}
}
class Color
{
public void M1<T>(T x, int y = 0)
{
System.Console.WriteLine("instance");
}
public static void M1<T>(T x) where T : unmanaged
{
System.Console.WriteLine("static");
}
}
Ha a fogadót Color értékként kezeljük, rögzítjük a paramétert, és az "S1" felügyelt lesz. Ezután a statikus metódus a kényszer miatt nem alkalmazható, és meghívjuk a példánymetódust. Ha azonban a fogadót típusként kezeljük, nem rögzítjük a paramétert, és az "S1" nem lesz kezelve, akkor mindkét módszer alkalmazható, de a statikus módszer "jobb", mert nem rendelkezik opcionális paraméterrel. Egyik választás sem vezet hibához, de mindegyik eltérő viselkedést eredményezne.
Ennek alapján a fordítóprogram egyértelműségi hibát fog eredményezni a taghozzáférés E.I esetén, ha az alábbi feltételek teljesülnek:
- A
E.Itagok keresése egyidejűleg egy példány- és statikus tagokat is tartalmazó tagcsoportot eredményez. A fogadótípusra vonatkozó kiterjesztési metódusokat ellenőrzés céljából példánymódszerként kezelik. - Ha a
Enem típusnévként, hanem egyszerű névként kezeli, az egy elsődleges konstruktorparaméterre hivatkozna, és a paramétert a beágyazási típus állapotába rögzítené.
Dupla tárolási figyelmeztetések
Ha egy elsődleges konstruktorparamétert adnak át az alapnak, és és is rögzítve vannak, nagy a kockázata annak, hogy véletlenül kétszer tárolják az objektumban.
A Fordító figyelmeztetést fog generálni a in-ra vagy érték szerinti argumentumra egy class_baseargument_list-ben, ha az alábbi feltételek teljesülnek:
- Az argumentum egy elsődleges konstruktorparaméter implicit vagy explicit identitásátalakítását jelenti;
- Az argumentum nem része kibontott
paramsargumentumnak; - Az elsődleges konstruktorparamétert a rendszer a beágyazási típus állapotába rögzíti.
A Fordító figyelmeztetést küld egy variable_initializer-ra, ha minden alábbi feltétel teljesül:
- A változó inicializálója egy elsődleges konstruktorparaméter implicit vagy explicit identitásátalakítását jelöli;
- Az elsődleges konstruktorparamétert a rendszer a beágyazási típus állapotába rögzíti.
Például:
public class Person(string name)
{
public string Name { get; set; } = name; // warning: initialization
public override string ToString() => name; // capture
}
Elsődleges konstruktorokat célzó attribútumok
https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-03-13.md úgy döntöttünk, hogy elfogadjuk a https://github.com/dotnet/csharplang/issues/7047 javaslatot.
A "módszer" attribútum cél megengedett a osztály_deklaráció/struktúra_deklaráció esetében, amelynek van paraméter_lista, és ennek eredményeként a megfelelő elsődleges konstruktor rendelkezik ezzel az attribútummal.
A method nélküli class_declaration/struct_declaration célértéket tartalmazó attribútumokat a rendszer figyelmeztetéssel hagyja figyelmen kívül.
[method: FooAttr] // Good
public partial record Rec(
[property: Foo] int X,
[field: NonSerialized] int Y
);
[method: BarAttr] // warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored.
public partial record Rec
{
public void Frobnicate()
{
...
}
}
[method: Attr] // Good
public record MyUnit1();
[method: Attr] // warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored.
public record MyUnit2;
Elsődleges konstruktorok rekordoknál
Ezzel a javaslattal a rekordoknak már nem kell külön megadniuk az elsődleges konstruktor-mechanizmust. Ehelyett az elsődleges konstruktorokkal rendelkező rekordok (osztály- és szerkezetdeklarációk) az általános szabályokat követik az alábbi egyszerű kiegészítésekkel:
- Minden elsődleges konstruktorparaméter esetében, ha egy azonos nevű tag már létezik, példánytulajdonságnak vagy mezőnek kell lennie. Ha nem, akkor az azonos nevű nyilvános init-only automatikus tulajdonság szintetizálása a paraméterből kiosztott tulajdonság inicializálójával történik.
- A dekonstruktor az elsődleges konstruktor paramétereinek megfelelő kimenő paraméterekkel van szintetizálva.
- Ha egy explicit konstruktor-deklaráció egy "másolási konstruktor" – olyan konstruktor, amely egyetlen paramétert vesz fel a beágyazási típusból – nem szükséges meghívni egy
thisinicializálót, és nem fogja végrehajtani a rekorddeklarációban szereplő tag inicializálókat.
Hátránya
- A létrehozott objektumok foglalási mérete kevésbé nyilvánvaló, mivel a fordító határozza meg, hogy az osztály teljes szövege alapján lefoglaljon-e mezőt egy elsődleges konstruktorparaméterhez. Ez a kockázat hasonló a lambdakifejezések változóinak implicit rögzítéséhez.
- Gyakori kísértés (vagy véletlen minta) lehet, hogy az "azonos" paramétert több öröklési szinten rögzíti, mivel az átadva van a konstruktorláncnak ahelyett, hogy explicit módon kiosztana egy védett mezőt az alaposztályban, ami az objektumok ugyanazon adatainak duplikált lefoglalásához vezet. Ez nagyon hasonlít az automatikus tulajdonságok automatikus tulajdonságokkal való felülírásának mai kockázatához.
- Az itt leírtaknak megfelelően nincs helye a konstruktortestekben általában kifejezhető további logikának. Az alábbi "elsődleges konstruktortestek" kiterjesztés megoldja ezt.
- A javasolt módon a végrehajtási sorrend szemantikája kissé eltér a szokásos konstruktorokétól, az alaphívások utánra késleltetve a tagok inicializálását. Ez valószínűleg orvosolható lenne, de a bővítési javaslatok némelyikének (különösen az "elsődleges konstruktori szerveknek") a költségén.
- A javaslat csak olyan helyzetekben működik, ahol egyetlen konstruktor jelölhető ki elsődlegesként.
- Az osztály és az elsődleges konstruktor külön akadálymentességét nem lehet kifejezni. Ilyen például, ha a nyilvános konstruktorok mindegyike egy privát "build-it-all" konstruktorhoz delegál. Szükség esetén a szintaxist később is javasolni lehet.
Alternatívák
Nincs rögzítés
A funkció sokkal egyszerűbb verziója megtiltaná az elsődleges konstruktorparaméterek tagtestületekben való előfordulását. A hivatkozásuk hiba lenne. A mezőket explicit módon kell deklarálni, ha a mezők tárolása az inicializálási kódon túl is kívánatos.
public class C(string s)
{
public string S1 => s; // Nope!
public string S2 { get; } = s; // Still allowed
}
Ez a későbbiekben teljes javaslattá fejlődhetne, és elkerülhetne számos döntést és komplexitást, a sablon elemek kezdeti kisebb eltávolításának áráért, és valószínűleg kevéssé tűnhet intuitívnak.
Kifejezetten létrehozott mezők
Alternatív módszer az elsődleges konstruktorparaméterek esetében, hogy mindig és láthatóan létrehoznak egy azonos nevű mezőt. Ahelyett, hogy a paramétereket a helyi és névtelen függvényekkel azonos módon zárná be, explicit módon létrejönne egy tagdeklaráció, hasonlóan a rekordok elsődleges construcor paramétereihez létrehozott nyilvános tulajdonságokhoz. A rekordokhoz hasonlóan, ha már létezik megfelelő tag, nem jön létre.
Ha a létrehozott mező privát, akkor is el lehet hagyni, ha tagtestületekben nem használják mezőként. Az osztályokban azonban a privát mező gyakran nem lenne a megfelelő választás, mert az származtatott osztályokban állapotduplikációt okozhat. Itt az lenne a lehetőség, hogy ehelyett létrehozunk egy védett mezőt az osztályokban, ösztönözve a tárterület újrafelhasználását az öröklési rétegek között. Ellenkező esetben nem tudnánk elrejteni a deklarációt, és minden elsődleges konstruktorparaméter miatt foglalási költségeink lennének.
Ez szorosabban összehangolná a nem rekord alapú konstruktorokat a rekordokkal, mivel a tagok mindig (legalábbis elméletileg) generálódnak, bár különböző típusú tagok különböző hozzáférési jogosultságokkal. Ez azonban meglepő különbségeket is eredményezne a paraméterek és a helyi adatok C#-ban máshol való rögzítésének módjától. Ha például engedélyezni szeretnénk a helyi osztályokat, akkor implicit módon rögzítenék a beágyazó paramétereket és a helyieket. Láthatóan létrehozni az árnyékoló mezőket nem tűnik egy ésszerű viselkedésnek.
Ezzel a megközelítéssel gyakran felmerült egy másik probléma, hogy sok fejlesztő eltérő elnevezési konvencióval rendelkezik a paraméterek és mezők esetében. Melyiket kell használni az elsődleges konstruktorparaméterhez? Bármelyik választás a kód többi részével való inkonzisztencia kialakulásához vezetne.
Végül a tagdeklarációk látható generálása valóban a rekordok lényege, de sokkal meglepőbb és szokatlan a nem rekord osztályok és struktúrák esetében. Mindent egybevetve, ezek az okok, amelyek miatt a fő javaslat az implicit háttérbeállítást választja, ésszerű viselkedéssel (a rekordokkal összhangban) az explicit tagdeklarációk esetében, amikor igény van rá.
Példánytagok eltávolítása az inicializáló hatóköréből
A fentebb említett keresési szabályok célja, hogy lehetővé tegyék az elsődleges konstruktorparaméterek jelenlegi viselkedésének megvalósulását a rekordokban, amikor a megfelelő tagot manuálisan deklarálják, illetve hogy megmagyarázzák a generált tag viselkedését, amikor ez nincs meg. Ehhez különbséget kell tenni az "inicializálási hatókör" (ez/alap inicializálók, tag inicializálók) és a "törzshatókör" (tagtestek) között, amelyet a fenti javaslat a elsődleges konstruktorparaméterek keresésekor a hivatkozás előfordulási helyétől függően módosításával ér el.
Megfigyelés, hogy egy egyszerű névvel rendelkező példánytagra való hivatkozás az inicializáló hatókörben mindig hibához vezet. Ahelyett, hogy a példánytagokat ezeken a helyeken csupán leárnyékolnánk, egyszerűen kivehetnénk őket a hatókörből? Így nem lenne ilyen furcsa feltételes sorrend a hatókörök között.
Ez az alternatíva valószínűleg lehetséges, de bizonyos következményekkel járna, amelyek valamelyest messzemenők és potenciálisan nemkívánatosak. Először is, ha eltávolítjuk a példánytagokat az inicializáló hatókörből, akkor egy egyszerű név, amely egy példánytagnak felel meg, és nem elsődleges konstruktorparaméterhez, véletlenül kapcsolódhat a típusdeklaráción kívüli dologhoz! Ez úgy tűnik, hogy ritkán lenne szándékos, és egy hiba jobb lenne.
Ezenkívül az inicializálási hatókörben statikus tagokra hivatkozni lehet. Ezért meg kell különböztetnünk a statikus és példánytagokat a lekérdezés során, amit ma nem teszünk. (Megkülönböztetünk a túlterhelés felbontásában, de ez itt nem játszik szerepet). Ezért ezt is módosítani kell, ami még több olyan helyzethez vezet, ahol például statikus környezetekben valami távolabb mutat anélkül, hogy hibát okozna, mivel egy példánytagot talált.
Mindezen "egyszerűsítés" elég nagy bonyodalmat okozna, amelyet senki sem kért.
Lehetséges bővítmények
Ezek olyan változatok vagy kiegészítések az alapjavaslathoz, amelyek együtt tekinthetők, vagy egy későbbi szakaszban, ha hasznosnak ítélik.
Elsődleges konstruktorparaméter-hozzáférés a konstruktorok között
A fenti szabályok miatt hiba egy elsődleges konstruktorparaméterre hivatkozni egy másik konstruktoron belül. Ez a más konstruktorok törzsében is engedélyezhető, mivel az elsődleges konstruktor fut először. A this inicializáló argumentumlistájában azonban nem engedélyezettnek kell maradnia.
public class C(bool b, int i, string s) : B(b)
{
public C(string s) : this(b, s) // b still disallowed
{
i++; // could be allowed
}
}
Az ilyen jellegű hozzáférés még mindig rögzítést eredményez, mivel ez lenne az egyetlen módja annak, hogy a konstruktor teste az elsődleges konstruktor lefutása után is hozzáférjen a változóhoz.
Az elsődleges konstruktorparaméterekre vonatkozó tilalom az inicializáló argumentumaiban gyengülhet, hogy lehetővé tegye őket, de nem feltétlenül rendeli hozzá őket, de ez nem tűnik hasznosnak.
Konstruktorok engedélyezése this inicializáló nélkül
Engedélyezhetők this inicializáló nélküli konstruktorok (például implicit vagy explicit base inicializálóval). Egy ilyen konstruktor nem futtatási példánymezőt, tulajdonságot és esemény inicializálókat, mivel ezek csak az elsődleges konstruktor részét képezik.
Ilyen alaphívású konstruktorok jelenlétében több lehetőség is van az elsődleges konstruktor paraméterrögzítésének kezelésére. A legegyszerűbb, ha ebben a helyzetben teljesen letiltjuk a rögzítést. Az elsődleges konstruktorparaméterek csak akkor inicializálhatók, ha ilyen konstruktorok léteznek.
Másik lehetőségként, ha a korábban ismertetett lehetőséggel kombinálva lehetővé teszi az elsődleges konstruktorparaméterek elérését a konstruktorokon belül, a paraméterek a konstruktor törzsébe nem feltétlenül hozzárendeltként léphetnek be, a rögzítetteket pedig a konstruktor törzsének végéhez kell rendelni. Ezek lényegében implicit kimeneti paraméterek lennének. Így a rögzített elsődleges konstruktorparaméterek mindig értelmes (azaz explicit módon hozzárendelt) értékkel rendelkeznek, mire más függvénytagok felhasználják őket.
Ennek a bővítménynek (mindkét formában) az a vonzereje, hogy teljes mértékben általánosítja a rekordokban a "másolási konstruktorok" aktuális kivételét, anélkül, hogy olyan helyzeteket eredményezne, ahol kezeletlen elsődleges konstruktorparaméterek figyelhetők meg. Lényegében az objektumot alternatív módon inicializáló konstruktorok rendben vannak. A rögzítéssel kapcsolatos korlátozások nem jelentenek kompatibilitástörő változást a rekordokban meglévő, manuálisan definiált másolási konstruktorok esetében, mivel a rekordok soha nem rögzítik az elsődleges konstruktorparamétereket (ehelyett mezőket hoznak létre).
public class C(bool b, int i, string s) : B(b)
{
public int I { get; set; } = i; // i used for initialization
public string S // s used directly in function members
{
get => s;
set => s = value ?? throw new ArgumentNullException(nameof(value));
}
public C(string s2) : base(true) // cannot use `string s` because it would shadow
{
s = s2; // must initialize s because it is captured by S
}
protected C(C original) : base(original) // copy constructor
{
this.s = original.s; // assignment to b and i not required because not captured
}
}
Elsődleges konstruktortestek
Maguk a konstruktorok gyakran tartalmaznak paraméterérvényesítési logikát vagy más nemtriviális inicializálási kódot, amely nem fejezhető ki inicializálóként.
Az elsődleges konstruktorok kiterjeszthetők, hogy az utasításblokkok közvetlenül az osztály törzsében jelenjenek meg. Ezek az utasítások a létrehozott konstruktorba lesznek beszúrva azon a ponton, ahol megjelennek az inicializálási hozzárendelések között, és így az inicializálókkal együttműködve lesznek végrehajtva. Például:
public class C(int i, string s) : B(s)
{
{
if (i < 0) throw new ArgumentOutOfRangeException(nameof(i));
}
int[] a = new int[i];
public int S => s;
}
Ennek a forgatókönyvnek a nagy része megfelelő lehet, ha olyan "végleges inicializálókat" vezetnénk be, amelyek a konstruktorok és minden objektum/gyűjtemény inicializálójának befejezése után futnak. Az argumentumérvényesítés azonban egy olyan dolog, amely ideális esetben a lehető leghamarabb megtörténik.
Az elsődleges konstruktorok törzse szintén helyet biztosíthat egy hozzáférési módosító megadásához az elsődleges konstruktor számára, így lehetővé téve, hogy az eltérjen a befoglaló típus hozzáférhetőségétől.
Kombinált paraméter- és tagdeklarációk
Lehetséges és gyakran említett kiegészítés lehet, hogy lehetővé teszi az elsődleges konstruktorparaméterek megjegyzését, hogy is tagot deklarálni a típushoz. Gyakran azt javasolják, hogy engedélyezzük a hozzáférési specifikáló használatát a paramétereknél, hogy ezzel aktiváljuk a taglétrehozást.
public class C(bool b, protected int i, string s) : B(b) // i is a field as well as a parameter
{
void M()
{
... i ... // refers to the field i
... s ... // closes over the parameter s
}
}
Vannak problémák:
- Mi van, ha egy tulajdonságra van szükség, nem mezőre? A paraméterlistában a
{ get; set; }szintaxis nem tűnik vonzónak. - Mi történik, ha különböző elnevezési konvenciók vannak használva paraméterekhez és mezőkhöz? Akkor ez a funkció haszontalan lenne.
Ez egy lehetséges jövőbeli kiegészítés, amelyet elfogadhatunk vagy nem. A jelenlegi javaslat nyitva hagyja a lehetőséget.
Kérdések megnyitása
Típusparaméterek keresési sorrendje
A https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/primary-constructors.md#lookup szakasz azt határozza meg, hogy a deklarálási típusú típusparamétereknek a típus elsődleges konstruktorparaméterei előtt kell lennie minden olyan környezetben, ahol ezek a paraméterek hatókörben vannak. Azonban már rendelkezünk meglévő viselkedéssel a rekordokkal – az elsődleges konstruktorparaméterek az alap inicializáló és a mező inicializálóinak típusparaméterei elé kerülnek.
Mit tegyünk ezzel az eltérésel?
- Módosítsa a szabályokat a viselkedésnek megfelelően.
- Módosítsa a viselkedést (lehetséges törési változás).
- Ne engedélyezze, hogy egy alapvető konstruktor paramétere a típusparaméter nevével egyezzen meg (egy lehetséges kompatibilitástörő változás).
- Ne tegyen semmit, fogadja el a specifikáció és a megvalósítás közötti ellentmondást.
Következtetés:
Módosítsa a szabályokat a viselkedésnek (https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-09-25.md#primary-constructors) megfelelően.
Mezőcélzási attribútumok rögzített elsődleges konstruktorparaméterekhez
Engedélyeznünk kell a mezőcélzási attribútumokat a rögzített elsődleges konstruktorparaméterekhez?
class C1([field: Test] int x) // Parameter is captured, the attribute goes to the capture field
{
public int X => x;
}
class C2([field: Test] int x) // Parameter is not captured, the attribute is ignored with a warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'param'. All attributes in this block will be ignored.
{
public int X = x;
}
A rendszer jelenleg figyelmen kívül hagyja az attribútumokat a figyelmeztetéssel, függetlenül attól, hogy a paraméter rögzítve van-e.
Vegye figyelembe, hogy rekordok esetében a mezőre célzott attribútumok akkor engedélyezettek, ha egy tulajdonság szintetizálva van hozzá. Az attribútumok ezután a háttérmezőre kerülnek.
record R1([field: Test]int X); // Ok, the attribute goes on the backing field
record R2([field: Test]int X) // warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'param'. All attributes in this block will be ignored.
{
public int X = X;
}
Következtetés:
Nem engedélyezett (https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-03.md#attributes-on-captured-parameters).
Figyelmeztetés az árnyékolásra egy tag által a bázisról
Jelentsünk-e figyelmeztetést, ha egy alaptag egy tagon belül egy elsődleges konstruktorparamétert árnyékolásba helyez (lásd https://github.com/dotnet/csharplang/discussions/7109#discussioncomment-5666621)?
Következtetés:
Alternatív tervet hagytak jóvá – https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-08.md#primary-constructors
A zárási típus példányának rögzítése egy lezárásban
Ha egy példány-inicializálón vagy egy alap inicializálón belül egy lambdában is hivatkoznak a beágyazási típus állapotába rögzített paraméterekre, a lambdának és a beágyazási típus állapotának ugyanarra a helyre kell hivatkoznia a paraméterhez. Például:
partial class C1
{
public System.Func<int> F1 = Execute1(() => p1++);
}
partial class C1 (int p1)
{
public int M1() { return p1++; }
static System.Func<int> Execute1(System.Func<int> f)
{
_ = f();
return f;
}
}
Mivel egy paraméter típusállapotba való rögzítésének naiv implementációja egyszerűen rögzíti a paramétert egy privát példánymezőben, a lambdának ugyanarra a mezőre kell hivatkoznia. Ennek eredményeképpen képesnek kell lennie a típuspéldány elérésére. Ehhez az alapkonstruktor meghívása előtt be kell rögzítenie this egy lezárásba. Ez viszont biztonságos, de nem ellenőrizhető IL-t eredményez. Ez elfogadható?
Alternatív megoldásként:
- Tiltsa meg az ilyen lambdákat;
- Vagy ehelyett rögzítse az ilyen paramétereket egy külön osztály egy példányában (még egy másik lezárás), és ossza meg a példányt a lezárás és a beágyazási típus példánya között. Így szükségtelenné téve a
thisrögzítését egy lezárás során.
Következtetés:
Az alapkonstruktor meghívása előtt kényelmesen rögzíthetjük this (https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-15.md).
A futtatókörnyezeti csapat sem találta az IL-mintát problémásnak.
Hozzárendelés this egy szerkezeten belül
A C# lehetővé teszi this hozzárendelését egy szerkezeten belül. Ha a szerkezet egy elsődleges konstruktorparamétert rögzít, a hozzárendelés felülírja az értékét, ami nem feltétlenül nyilvánvaló a felhasználó számára. Jelentést szeretnénk tenni az ilyen feladatokhoz kapcsolódó figyelmeztetésről?
struct S(int x)
{
int X => x;
void M(S s)
{
this = s; // 'x' is overwritten
}
}
Következtetés:
Engedélyezett, figyelmeztetés nélkül (https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-15.md).
Dupla tárolási figyelmeztetés inicializáláshoz és rögzítéshez
Figyelmeztetést kapunk, ha egy elsődleges konstruktor paramétert továbbítanak az alap osztálynak, és , valamint is rögzítve van, mert nagy a kockázata annak, hogy akaratlanul kétszer kerül tárolásra az objektumban.
Úgy tűnik, hogy hasonló kockázat áll fenn, ha egy paraméterrel inicializál egy tagot, és a rendszer rögzíti is. Íme egy kis példa:
public class Person(string name)
{
public string Name { get; set; } = name; // initialization
public override string ToString() => name; // capture
}
A Personegy adott példánya esetében a Name változásai nem tükröződnek a ToStringkimenetében, ami valószínűleg a fejlesztő részéről nem szándékos.
Vezessen be dupla tárolási figyelmeztetést erre a helyzetre?
Ez így működik:
A fordító figyelmeztetést küld egy variable_initializer miatt, ha az alábbi feltételek teljesülnek:
- A változó inicializálója egy elsődleges konstruktorparaméter implicit vagy explicit identitásátalakítását jelöli;
- Az elsődleges konstruktorparamétert a rendszer a beágyazási típus állapotába rögzíti.
Következtetés:
Jóváhagyás: https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-15.md#primary-constructors
LDM-értekezletek
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-10-17.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-01-18.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-15.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-22.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-03-13.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-03.md#primary-constructors
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-08.md#primary-constructors
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-15.md#primary-constructors
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-09-25.md#primary-constructors
C# feature specifications