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/5737
Összefoglalás
Ez a funkció lehetővé teszi, hogy a szerkezetkonstruktorokban azonosítsuk azokat a mezőket, amelyeket a felhasználó nem osztott ki explicit módon a visszatérés vagy használat előtt, és implicit módon inicializáljuk őket a default ahelyett, hogy határozott hozzárendelési hibákat adnánk meg.
Motiváció
Ez a javaslat a dotnet/csharplang#5552 és a dotnet/csharplang#5635 használhatósági problémáinak lehetséges enyhítése, valamint a #5563 címke kezelése érdekében lett felvetve (minden mezőnek egyértelműen hozzá kell lennie rendelve, de a field nem elérhető a konstruktoron belül).
A C# 1.0 óta a szerkezetkonstruktoroknak egyértelműen this kell hozzárendelniük, mintha out paraméter lenne.
public struct S
{
public int x, y;
public S() // error: Fields 'S.x' and 'S.y' must be fully assigned before control is returned to the caller
{
}
}
Ez problémákat okozhat, ha a beállítók manuálisan vannak definiálva félautomata tulajdonságokon, mivel a fordító nem tudja a tulajdonság hozzárendelését a háttérmező hozzárendelésével egyenértékűként kezelni.
public struct S
{
public int X { get => field; set => field = value; }
public S() // error: struct fields aren't fully assigned. But caller can only assign 'this.field' by assigning 'this'.
{
}
}
Feltételezzük, hogy a beállítókra vonatkozó részletesebb korlátozások bevezetése, például egy olyan séma, amelyben a beállító nem veszi ref this, hanem inkább paraméterként veszi out field, bizonyos használati esetekben túl szűk és hiányos lesz.
Az egyik alapvető feszültség, amellyel küzdünk, hogy amikor a strukturális tulajdonságokhoz manuálisan implementálnak settereket, a felhasználóknak gyakran valamilyen "ismétlődést" kell végezniük, vagy újra kell hozzárendelniük, vagy megismételniük a logikájukat.
struct S
{
private int _x;
public int X
{
get => _x;
set => _x = value >= 0 ? value : throw new ArgumentOutOfRangeException();
}
// Solution 1: assign some value in the constructor before "really" assigning through the property setter.
public S(int x)
{
_x = default;
X = x;
}
// Solution 2: assign the field once in the constructor, repeating the implementation of the setter.
public S(int x)
{
_x = x >= 0 ? x : throw new ArgumentOutOfRangeException();
}
}
Előző vitafórum
Egy kis csoport megvizsgálta ezt a problémát, és megfontolt néhány lehetséges megoldást:
- Megköveteli a felhasználóktól, hogy
this = default-t rendeljenek hozzá, ha a félig automatikus tulajdonságok esetén manuálisan implementált beállítókat használnak. Egyetértünk abban, hogy ez nem a megfelelő megoldás, mivel elfújja a mező inicializálóiban beállított értékeket. - Implicit módon inicializálja az automatikus/félig automatikus tulajdonságok összes háttérmezőjét.
- Ez megoldja a "félig automatikus tulajdonság beállítók" problémát, és a kifejezetten deklarált mezőket más szabályok alá helyezi: "ne inicializálja implicit módon a mezőket, azonban implicit módon inicializálja az automatikus tulajdonságokat."
- Adjon meg módot egy félig automatikus tulajdonság háttérmezőjének hozzárendelésére, és megkövetelheti a felhasználóktól a hozzárendelést.
- Ez nehézkes lehet a (2) ponthoz képest. Az automatikus tulajdonságnak "automatikusnak" kell lennie, és lehet, hogy magában foglalja a mező "automatikus" inicializálását. Zavart okozhat, hogy mikor rendeli hozzá a mögöttes mezőt egy hozzárendelés a tulajdonsághoz, és mikor hívja meg a tulajdonság-beállítót.
visszajelzést is kaptunk felhasználóktól, akik például néhány mező inicializálót szeretnének belefoglalni a szerkezetekbe anélkül, hogy mindent explicit módon kellene hozzárendelni. Ezt a problémát, valamint a "félig automatikus tulajdonság manuálisan implementált beállítóval" problémát is meg tudjuk oldani egyszerre.
struct MagnitudeVector3d
{
double X, Y, Z;
double Magnitude = 1;
public MagnitudeVector3d() // error: must assign 'X', 'Y', 'Z' before returning
{
}
}
Határozott hozzárendelés módosítása
Ahelyett, hogy a nem hozzárendelt mezők hibáinak meghatározása érdekében határozott hozzárendelés-elemzést végeznénk this, azt végezzük el, hogy meghatározzuk, mely mezőket kell implicit módon inicializálni. Az ilyen inicializálás a konstruktor elején történik.
struct S
{
int x, y;
// Example 1
public S()
{
// ok. Compiler inserts an assignment of `this = default`.
}
// Example 2
public S()
{
// ok. Compiler inserts an assignment of `y = default`.
x = 1;
}
// Example 3
public S()
{
// valid since C# 1.0. Compiler inserts no implicit assignments.
x = 1;
y = 2;
}
// Example 4
public S(bool b)
{
// ok. Compiler inserts assignment of `this = default`.
if (b)
x = 1;
else
y = 2;
}
// Example 5
void M() { }
public S(bool b)
{
// ok. Compiler inserts assignment of `y = default`.
x = 1;
if (b)
M();
y = 2;
}
}
A (4) és (5) példákban az eredményül kapott kódgenerálás néha a mezők duplikált hozzárendelésével jár. Ez általában rendben van, de azoknak a felhasználóknak, akik aggódnak az ilyen kettős hozzárendelések miatt, kibocsáthatunk egy korábban határozott hozzárendelési hibaként ismert diagnosztikasorozatot, amelyet alapértelmezés szerint letiltott figyelmeztető diagnosztikává formáltunk.
struct S
{
int x;
public S() // warning: 'S.x' is implicitly initialized to 'default'.
{
}
}
Azok a felhasználók, akik a diagnosztikát "hiba" értékre állítják be, a C# 11 előtti viselkedést fogják választani. Az ilyen felhasználók lényegében nem férhetnek hozzá a félig automatizált tulajdonságokhoz, ahol a beállítók manuálisan lettek implementálva.
struct S
{
public int X
{
get => field;
set => field = field < value ? value : field;
}
public S() // error: backing field of 'S.X' is implicitly initialized to 'default'.
{
X = 1;
}
}
Első pillantásra ez olyan, mint egy "lyuk" a funkción, de ez valójában ez a helyes megoldás. A diagnosztika engedélyezésével a felhasználó azt mondja nekünk, hogy nem szeretné, hogy a fordító implicit módon inicializálja a mezőket a konstruktorban. Itt nem lehet elkerülni az implicit inicializálást, ezért a megoldás számukra az, hogy más módon inicializálják a mezőt, mint egy manuálisan implementált beállító, például a mező manuális deklarálása és hozzárendelése, vagy egy mező inicializálása.
A JIT jelenleg nem szünteti meg a holt tárolókat a ref-eken keresztül, ami azt jelenti, hogy ezek az implicit inicializálások valós költségekkel járnak. De ez javítható. https://github.com/dotnet/runtime/issues/13727
Érdemes megjegyezni, hogy az egyes mezők inicializálása a teljes példány helyett valójában csak optimalizálás. A fordítónak valószínűleg szabadon alkalmazhat bármilyen heurisztikát, amennyiben megfelel annak az invariánsnak, hogy azokat a mezőket, amelyek nincsenek egyértelműen hozzárendelve egyik visszatérési pontnál sem vagy mielőtt a this bármely nem mezőtaghoz való hozzáférés történne, implicit módon inicializálni kell.
Ha például egy struktúra 100 mezővel rendelkezik, és csak az egyiket inicializálja explicit módon, logikusabb lehet az egészre initobj-t alkalmazni, mint hogy a másik 99 mezőhöz initobj-et implicit módon kibocsássunk. A 99 további mezőhöz implicit módon initobj kibocsátó implementáció azonban továbbra is érvényes lenne.
A nyelvi specifikáció módosítása
A szabvány következő szakaszát módosítjuk:
https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12814-this-access
Ha a konstruktor deklarációja nem rendelkezik konstruktor inicializálóval, a
thisváltozó pontosan ugyanúgy viselkedik, mint a szerkezettípusoutparamétere. Ez különösen azt jelenti, hogy a változót mindenképpen hozzá kell rendelni a példánykonstruktor minden végrehajtási útvonalához.
Ezt a nyelvet a következőképpen módosítjuk:
Ha a konstruktor deklarációja nem rendelkezik konstruktor-inicializálóval, a this változó a szerkezettípus out paraméteréhez hasonlóan viselkedik, azzal a kivételével, hogy nem hiba, ha a határozott hozzárendelési követelmények (§9.4.1) nem teljesülnek. Ehelyett a következő viselkedéseket vezetjük be:
- Ha maga a
thisváltozó nem felel meg a követelményeknek, akkor a rendszer implicit módon inicializálja azthisfázisban lévő összes nem hozzárendelt példányváltozót az minden olyan pontján, ahol a követelmények sérülnek, implicit módon inicializálódik az alapértelmezett értékre (§9.3) egythisfázisban, mielőtt a konstruktor bármely más kódja lefut. - Ha egy példányváltozó v
thisnem felel meg a követelményeknek, vagy a v bármely beágyazási szintjén lévő példányváltozó nem felel meg a követelményeknek, akkor v implicit módon inicializálódik egy inicializálási fázis alapértelmezett értékére, mielőtt a konstruktor bármely más kódja lefut.
Tervezői értekezletek
C# feature specifications