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/9662
Összefoglalás
A Unions olyan összekapcsolt funkciók készlete, amelyek egyesítve C#-támogatást nyújtanak az egyesítő típusok számára:
-
Egyesítő típusok: Az attribútummal rendelkező
[Union]szerkezetek és osztályok egyesítő típusokként vannak felismerve, és támogatják az egyesítő viselkedést. - Esettípusok: Az egyesítő típusok esettípusok készletével rendelkeznek, amelyeket a konstruktorok és a gyári metódusok paraméterei adnak meg.
-
Union behaviors: Union types support the következő union behaviors:
- Union conversions: Implicit union conversions from each case type to a union type.
- Unióegyezés: A mintaegyezés az egyesítő értékekhez implicit módon "feloldja" a tartalmát, és ehelyett alkalmazza a mintát az alapul szolgáló értékre.
- Unió teljessége: Az egyesítő értékekre vonatkozó kifejezések teljes körűek, ha az összes esettípus megfeleltetve lett, tartalék eset nélkül.
- Union nullability: A nullability analysis továbbfejlesztett nyomon követi az egyesítők tartalmának null állapotát.
- Egyesítő minták: Minden egyesítő típus egy alapszintű egyesítési mintát követ, de adott forgatókönyvekhez további választható minták is rendelkezésre állnak.
- Union deklarációk: A rövidített szintaxis lehetővé teszi az egyesítő típusok közvetlen deklarálását. A megvalósítás "véleményezett" - egy strukturált deklaráció, amely követi az alapvető egyesítési mintát, és egyetlen referenciamezőként tárolja a tartalmat.
- Uniós interfészek: A nyelv néhány interfészt ismert, és az uniós deklarációk végrehajtásához használják.
Motiváció
Az egyesítések egy régóta kért C#-funkció, amely lehetővé teszi az értékek zárt halmazból való kifejezését oly módon, hogy a mintaegyezés megbízható legyen a teljesség igénye nélkül.
Az egyesítő típusok és az egyesítő deklarációk elkülönítése lehetővé teszi, hogy a C# tömör union deklarációszintaxissal rendelkezzen a véleményezett szemantikával, ugyanakkor lehetővé teszi a meglévő típusok vagy típusok más implementációs választási lehetőségekkel való használatát is.
A C#-ban javasolt szakszervezetek nem "diszkriminált" vagy "címkézett" típusú szakszervezetek. A "diszkriminált szakszervezetek" "típusegyesítőkként" kifejezhetők új típusú deklarációk használatával esettípusokként. Alternatív megoldásként egy zárt hierarchiaként is implementálhatók, amely egy másik, kapcsolódó, közelgő C# funkció, amely a teljességre összpontosít.
Részletes kialakítás
Egyesítő típusok
Az attribútummal rendelkező System.Runtime.CompilerServices.UnionAttribute osztály- vagy struktúratípusok egyesítő típusnak minősülnek:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(Class | Struct, AllowMultiple = false)]
public class UnionAttribute : Attribute;
}
A szakszervezeti típusnak a szakszervezeti tagok bizonyos mintáját kell követnie, amelyet vagy az unió típusán kell deklarálni, vagy egy "szakszervezeti tagszolgáltatónak" delegálni.
Egyes szakszervezeti tagok kötelezőek, míg mások nem kötelezőek.
Az egyesítő típusok esettípusai bizonyos szakszervezeti tagok aláírása alapján jönnek létre.
Az egyesítő értékek tartalma egy Value tulajdonságon keresztül érhető el. A nyelv feltételezi, hogy Value mindig csak az egyik esettípus vagy null értéket tartalmazza (lásd a jól formázottságot).
Uniós tagszolgáltatók
Alapértelmezés szerint az egyesítő tagok az egyesítő típuson találhatók. Ha azonban az unió típusa közvetlenül tartalmaz egy interfész deklarációját, IUnionMembers akkor az interfész uniótag szolgáltatóként működik. Ebben az esetben a szakszervezeti tagok csak a szakszervezeti tagszolgáltatónál találhatók, maga az unió típusa nem.
A szakszervezeti tagok szolgáltatói felületének nyilvánosnak kell lennie, és magának az uniótípusnak interfészként kell implementálnia.
A union-defining type kifejezést arra a típusra használjuk, amelyben a szakszervezeti tagok találhatók: A szakszervezeti tag szolgáltató, ha létezik, és az unió másként írja be magát.
Uniós tagok
Az unió tagjait név és aláírás alapján keresik az uniót meghatározó típus alapján. Ezeket nem kell közvetlenül az unió-meghatározó típuson deklarálni, hanem örökölhető.
Hiba, hogy a szakszervezeti tagok nem lehetnek nyilvánosak.
A létrehozási tagok és a Value tulajdonság kötelezőek, és együttesen az alapvető egyesülési mintának nevezik.
A HasValue tagok és TryGetValue a tagok együttesen a nem boxing union hozzáférési minta.
A különböző szakszervezeti tagokról az alábbiakban olvashat.
Uniós létrehozási tagok
Az uniólétrehozó tagok új egyesítő értékeket hoznak létre egy esettípus értékéből.
Ha az egyesítő-meghatározó típus maga az egyesítő típus, minden konstruktor egyetlen paraméterrel egy egyesítő konstruktor. A konstruktorok paramétertípusaiból összeállított típuskészletként az egyesítő esettípusokat a következő módon azonosítjuk:
- Ha a paramétertípus null értékű (akár érték, akár hivatkozás), a kis- és nagybetűk típusa az alapul szolgáló típus
- Ellenkező esetben a kis- és nagybetűk típusa a paraméter típusa.
// Union constructor making `Dog` a case type
public Pet(Dog value) { ... }
// Union constructor making `int` a case type
public Union(int? value) { ... }
// Union constructor making `string` a case type
public Union(string? value) { ... }
Ha az unió-meghatározó típus egy uniótag-szolgáltató, minden statikus Create metódus egyetlen paraméterrel és egy identitás-átalakítható visszatérési típussal maga az unió-előállító metódus.
Az egyesítő esettípusokat a következő módon azonosítjuk az ilyen gyári metódusok paramétertípusaiból létrehozott típuskészletként:
- Ha a paramétertípus null értékű (akár érték, akár hivatkozás), a kis- és nagybetűk típusa az alapul szolgáló típus
- Ellenkező esetben a kis- és nagybetűk típusa a paraméter típusa.
// Union factory method making `Cat` a case type
public static Pet Create(Cat value) { ... }
// Union factory method making `int` a case type
public static Union Create(int? value) { ... }
// Union factory method making `string` a case type
public static Union Create(string? value) { ... }
Az unió konstruktorait és az union factory metódusokat együttesen union creation tagoknak nevezzük.
Az egyesítő létrehozási tagok egyetlen paraméterének egy by-value vagy in paraméternek kell lennie.
Az egyesítő típusnak legalább egy uniólétrehozó tagot, tehát legalább egy esettípust kell tartalmaznia.
Érték tulajdonság
A Value tulajdonság lehetővé teszi az egyesítőben található érték elérését, az eset típusától függetlenül.
Minden egyesítő-meghatározó típusnak egy vagy több típusú object?objecttulajdonságot Value kell deklarálnia. A tulajdonságnak rendelkeznie kell egy tartozékkal get , és opcionálisan rendelkeznie kell egy init vagy set tartozékkal, amely bármilyen akadálymentes lehet, és amelyet a fordító nem használ.
// Union 'Value' property
public object? Value { get; }
Nem boxing access tagok
Az egyesítő típus dönthet úgy, hogy a nem boxing union hozzáférési mintát is implementálja, amely lehetővé teszi az egyes esettípusokhoz való szigorúan gépelt feltételes hozzáférést, valamint a null érték ellenőrzésének módját.
Ez lehetővé teszi a fordító számára, hogy hatékonyabban implementálja a mintaegyezést, ha az esettípusok értéktípusok, és az egyesítésben ilyenként vannak tárolva.
A nem boxing hozzáférési tagok a következők:
- Nyilvános
HasValuetartozékkalgetrendelkező típustulajdonságbool. Opcionálisan rendelkezhet olyan tartozékkalinitvagysettartozékkal, amely bármilyen akadálymentes lehet, és amelyet a fordító nem használ. - Egy
TryGetValuemetódus minden esettípushoz. A metódus egy identitás-átalakítható típus egyetlen kimenő paraméterét adja visszaboolés veszi át az esettípusra.
// Non-boxing access members
public bool HasValue { get { ... } }
public bool TryGetValue(out Dog value) { ... }
HasValue akkor és csak akkor tér vissza igaz értékre, ha az unió Value értéke nem null.
TryGetValue a függvény akkor és csak akkor ad vissza igaz értéket, ha az egyesítő Value értéke az adott esettípus, és ha igen, akkor adja meg ezt az értéket a metódus out paraméterében.
Jól formázott
A nyelv és a fordító számos viselkedési feltételezést tesz az egyesítő típusokkal kapcsolatban. Ha egy típus egyesítő típusnak minősül, de nem felel meg ezeknek a feltételezéseknek, akkor előfordulhat, hogy az egyesítő viselkedések nem a várt módon működnek.
-
Hangképesség: A
Valuetulajdonság mindig null értékűre vagy kis- és nagybetűkre értékelhető ki. Ez még az egyesítő típus alapértelmezett értékére is igaz. -
Stabilitás: Ha egy kis- és nagybetűtípusból jön létre egyesítő érték, a
Valuetulajdonság megegyezik az adott esettípussal vagy null értékkel. Ha egy értékből létrejön egynullegyesítő érték, a tulajdonság aValuekövetkező lesznull: . - Létrehozási egyenértékűség: Ha egy érték implicit módon két különböző esettípusra konvertálható, akkor az ilyen esettípusok létrehozási tagjának ugyanaz a megfigyelhető viselkedése, amikor az adott értékkel meghívják.
-
Hozzáférési minta konzisztenciája: A nem dobozoló hozzáférési tagok viselkedése
HasValueTryGetValue, ha van ilyen, megfigyelhetően egyenértékű aValuetulajdonsággal való közvetlen ellenőrzéssel.
Példák egyesítő típusokra
Pet megvalósítja az alapszintű egyesítő mintát az egyesítő típuson:
[Union] public record struct Pet
{
// Creation members = case types are 'Dog' and 'Cat'
public Pet(Dog value) => Value = value;
public Pet(Cat value) => Value = value;
// 'Value' property
public object? Value { get; }
}
IntOrBool megvalósítja a nem boxing hozzáférési mintát az egyesítő típuson:
public record struct IntOrBool
{
private bool _isBool;
private int _value;
public IntOrBool(int value) => (_isBool, _value) = (false, value);
public IntOrBool(bool value) => (_isBool, _value) = (true, value ? 1 : 0);
public object Value => _isBool ? _value is 1 : _value;
public bool HasValue => true;
public bool TryGetValue(out int value)
{
value = _value;
return !_isBool;
}
public bool TryGetValue(out bool value)
{
value = _isBool && _value is 1;
return _isBool;
}
}
Megjegyzés: Ez csak egy példa a nem boxing hozzáférési minta implementálására. A felhasználói kód tetszőleges módon tárolhatja a tartalmat. Különösen nem akadályozza meg a megvalósítást a boxolásban! A non-boxing nevében az szerepel, hogy lehetővé teszi a fordító mintamegfelelő implementációjának, hogy az egyes esettípusokat erősen gépelt módon érje el, szemben a object?-typed Value tulajdonságtal.
Result<T> az alapszintű mintát egy uniós tagszolgáltatón keresztül valósítja meg:
public record class Result<T> : Result<T>.IUnionMembers
{
object? _value;
public interface IUnionMembers
{
public static Result<T> Create(T value) => new() { _value = value };
public static Result<T> Create(Exception value) => new() { _value = value };
public object? Value { get; }
}
object? IUnionMembers.Value => _value;
}
Egyesítő viselkedések
Az egyesítő viselkedések általában az alapszintű egyesítő mintával vannak implementálva. Ha az unió a nem boxing hozzáférési mintát kínálja, az egyesítő minta egyeztetése előnyösen használja azt.
Uniós átalakítások
Az egyesítő átalakítások implicit módon egyesítő típussá alakulnak az egyes esettípusokból. Konkrétan egy típusból vagy kifejezésből E egy egyesítő típusra U való egyesítési átalakítást kell létrehozni, ha egy standard implicit átalakítás típusról E típusra C történik, és C az egyesítő létrehozási tag paramétertípusaU.
Ha az egyesítő típus U egy struktúra, akkor egy típusból vagy kifejezésből E típusra U? való egyesítési átalakítás történik, ha egy standard implicit átalakítás típusról E típusra C történik, és C paramétertípusa az egyesítő létrehozási tagnakU.
Az egyesítő átalakítás önmagában nem szabványos implicit konverzió. Ezért nem vehet részt felhasználó által definiált implicit átalakításban vagy más egyesítő átalakításban.
Az implicit union konverziókon túl nincsenek explicit union konverziók. Így még akkor is, ha explicit átalakítás történik egy unió esettípusára EC, ez nem jelenti azt, hogy explicit átalakítás történik az adott egyesítő típusra E .
A szakszervezeti átalakítás az unió létrehozó tagjának meghívásával történik:
Pet pet = dog;
// becomes
Pet pet = new Pet(dog);
// and
Result<string> result = "Hello"
//becomes
Result<string> result = Result<string>.IUnionMembers.Create("Hello");
Hiba, ha a túlterhelés feloldása nem talál egyetlen legjobb jelöltet, vagy ha ez a tag nem tagja az unió típusú szakszervezeti tagoknak.
Az unió-átalakítás csak egy implicit, felhasználó által definiált átalakítás "formája". A felhasználó által definiált konverziós operátor "árnyékok" egyesítő konverziója.
A döntés indoklása:
Ha valaki felhasználó által definiált operátort írt, prioritást kell kapnia. Más szóval, ha a felhasználó ténylegesen megírta a saját operátorát, azt akarják, hogy hívjuk meg. Az egyesítő típusokká átalakított konverziós operátorokkal rendelkező meglévő típusok továbbra is ugyanúgy működnek, mint a meglévő, az operátorokat használó kód esetében.
Az alábbi példában egy implicit, felhasználó által definiált átalakítás elsőbbséget élvez az egyesítő átalakítással szemben.
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(int x) => ...
public S1(string x) => ...
object System.Runtime.CompilerServices.IUnion.Value => ...
public static implicit operator S1(int x) => ...
}
class Program
{
static S1 Test1() => 10; // implicit operator S1(int x) is used
static S1 Test2() => (S1)20; // implicit operator S1(int x) is used
}
Az alábbi példában, amikor az explicit leadás kódban van használva, egy explicit, felhasználó által definiált átalakítás elsőbbséget élvez az egyesítő átalakítással szemben. Ha azonban nincs explicit osztás a kódban, a rendszer egyesítő konverziót használ, mert az explicit, felhasználó által definiált átalakítás nem alkalmazható.
struct S2 : System.Runtime.CompilerServices.IUnion
{
public S2(int x) => ...
public S2(string x) => ...
object System.Runtime.CompilerServices.IUnion.Value => ...
public static explicit operator S2(int x) => ...
}
class Program
{
static S2 Test3() => 10; // Union conversion S2.S2(int) is used
static S2 Test4() => (S2)20; // explicit operator S2(int x)
}
Unió egyeztetése
Ha egy minta bejövő értéke egyesítő típusú vagy egyező típusú null értékű, a null értékű érték és az alapul szolgáló egyesítő érték tartalma a mintától függően "le van bontva".
A feltétel nélküli _ és var a minták esetében a rendszer a bejövő értékre alkalmazza a mintát. Például:
if (GetPet() is var pet) { ... } // 'pet' is the union value returned from `GetPet`
Az összes többi minta azonban implicit módon lesz alkalmazva az alapul szolgáló unió tulajdonságára Value :
if (GetPet() is Dog dog) { ... } // 'Dog dog' is applied to 'GetPet().Value'
if (GetPet() is null) { ... } // 'null' is applied to 'GetPet().Value'
if (GetPet() is { } value) { ... } // '{ } value' is applied to 'GetPet().Value'
Logikai minták esetén ezt a szabályt a rendszer egyenként alkalmazza az ágakra, szem előtt tartva, hogy a and minta bal ága hatással lehet a jobb ág bejövő típusára:
GetPet() switch
{
var pet and not null => ... // 'var pet' applies to the incoming 'Pet' and 'not null' to its 'Value'
not null and var value => ... // 'not null' applies to the 'Value' as does 'var value' because of the
// left branch changing the incoming type to `object?`.
}
Megjegyzés: Ez a szabály azt jelenti, hogy GetPet() is Pet pet valószínűleg nem fog sikerülni, ahogy Pet az a tartalomra vonatkozik, nem pedig magára az Pet unióra.
Megjegyzés: A feltétel nélküli var minta eltérő kezelésének oka (és _lényegében rövidítés var _) az a feltételezés, hogy használatuk minőségileg eltér a többi mintától.
var A minták egyszerűen a megfeleltetendő érték elnevezésére szolgálnak, gyakran beágyazott mintákban, például PetOwner{ Pet: var pet }. Itt a hasznos szemantika az pet , hogy megtartsa az egyesítő típust Pet, ahelyett, hogy a Value tulajdonságot használhatatlan object? típusra halasztanák.
Ha a bejövő érték osztálytípus, akkor a null minta sikeres lesz, függetlenül attól, hogy maga null az egyesítő érték vagy a benne foglalt érték:null
if (result is null) { ... } // if (result == null || result.Value == null)
Más egyesítő minták csak akkor lesznek sikeresek, ha maga az egyesítő érték nem null.
if (result is 1) { ... } // if (result != null && result.Value is 1)
Hasonlóképpen, ha a bejövő érték null értékű (struct union type körbefuttatása), akkor a null minta akkor is sikeres lesz, függetlenül attól, hogy maga a bejövő érték vagy null a benne foglalt érték:null
if (result is null) { ... } // if (result.HasValue == false || result.GetValueOrDefault().Value == null)
Más egyesítő minták csak akkor lesznek sikeresek, ha maga a bejövő érték nem null.
if (result is 1) { ... } // if (result.HasValue && result.GetValueOrDefault().Value is 1)
A fordító inkább a nem boxing hozzáférési minta által előírt tagok használatával valósítja meg a minta viselkedését. Bár a jól formázottsági szabályok keretei között szabadon végezhet optimalizálást, a következők a minimálisan garantáltan alkalmazandó készlet:
- Egy olyan minta esetében, amely azt jelenti, hogy egy adott típust
Tkell ellenőrizni , ha elérhető egyTryGetValue(S value)metódus, és van egy identitás, vagy implicit referencia/boxing konverzió a célhozTS, akkor a rendszer ezt a metódust használja az érték lekéréséhez. A rendszer ezt követően alkalmazza a mintát erre az értékre. Ha több ilyen módszer is létezik, akkor minden olyan esetben, ahol a konvertálásTSnem boxing konverzió, előnyben részesíti, ha van ilyen. Ha még mindig több módszer létezik, a rendszer implementáció által meghatározott módon választ ki egyet. - Ellenkező esetben egy olyan minta esetében, amely azt jelenti, hogy a
nullrendszer ellenőrzi ,HasValuehogy van-e elérhető tulajdonság, akkor a rendszer az adott tulajdonságot használja annak ellenőrzésére, hogy az egyesítő érték null-e. - Ellenkező esetben a rendszer alkalmazza a mintát a tulajdonság bejövő egyesítésen való elérésének
IUnion.Valueeredményére.
Az egyesítő típusra alkalmazott is-típus operátor jelentése megegyezik az egyesítő típusra alkalmazott típusmintával.
Az unió teljessége
Az egyesítő típusokat az esettípusok "kimerültnek" tekintik. Ez azt jelenti, hogy egy switch kifejezés teljes körű, ha egy unió összes esettípusát kezeli:
var name = pet switch
{
Dog dog => ...,
Cat cat => ...,
// No warning about non-exhaustive switch
};
Null lehetőség
Az egyesítő tulajdonság nullállapotát Value a rendszer a többi tulajdonsághoz hasonlóan követi nyomon a következő módosításokkal:
- Ha egy uniólétrehozó tagot hív meg (explicit módon vagy szakszervezeti átalakítással), az új unió
Valuemegkapja a bejövő érték null állapotát. - Ha a nem dobozoló hozzáférési minta
HasValueTryGetValue(...)egy egyesítési típus tartalmának lekérdezésére szolgál (explicit módon vagy mintaegyeztetéssel), az ugyanúgy befolyásoljaValuea nullképességi állapotot, minthaValueközvetlenül ellenőrizték volna: Az ág null állapotaValue"nem null"truelesz.
Még akkor is, ha az egyesítő kapcsoló egyébként teljes, ha a bejövő unió tulajdonságának Value null állapota "talán null", a rendszer figyelmeztetést ad a kezeletlen null értékre.
Pet pet = GetNullableDog(); // 'pet.Value' is "maybe null"
var value = pet switch
{
Dog dog => ...,
Cat cat => ...,
// Warning: 'null' not handled
}
Uniós interfészek
A következő felületeket használja a nyelv az egyesítő funkciók megvalósításában.
Union access interface
A IUnion felület fordításkor egyesítő típusként jelöl meg egy típust, és lehetővé teszi az egyesítő tartalmak futásidőben való elérését.
public interface IUnion
{
// The value of the union or null
object? Value { get; }
}
A fordító által létrehozott egyesítők implementálják ezt a felületet.
Példahasználat:
if (value is IUnion { Value: null }) { ... }
Uniós nyilatkozatok
Az uniós deklarációk tömör és véleményezett módon deklarálják az unió típusait a C#-ban. Deklarálnak egy szerkezetet, amely egyetlen objektumhivatkozást használ annak tárolásához Value, ami azt jelenti:
- Boxing: Az esettípusaikban szereplő értéktípusok a bejegyzéskor lesznek bejelölve.
- Tömörítés: Az egyesítő értékek csak egyetlen mezőt tartalmaznak.
A szándék az, hogy az unió nyilatkozatai a használati esetek túlnyomó többségét elég szépen lefedik. Az egyes uniótípusok kézi kódolásának két fő oka a szakszervezeti deklarációk használata helyett a következő:
- Meglévő típusok igazítása az egyesítési mintákhoz az egyesítő viselkedések eléréséhez.
- Egy másik tárolási stratégia megvalósítása például hatékonysági vagy interopop okból.
Szemantika
Az egyesítő deklarációk neve és az egyesítő konstruktorok típusainak listája.
union_declaration
: attributes? struct_modifier* 'partial'? 'union' identifier type_parameter_list?
'(' type (',' type)* ')' struct_interfaces? type_parameter_constraints_clause*
(`{` struct_member_declaration* `}` | ';')
;
A tagokra vonatkozó korlátozásokon (16.3.§) kívül a következők vonatkoznak a szakszervezeti tagokra:
- A példánymezők, az automatikus tulajdonságok vagy a mezőszerű események nem engedélyezettek.
- Explicit módon deklarált nyilvános konstruktorok egyetlen paraméterrel nem engedélyezettek.
- A explicit módon deklarált konstruktoroknak inicializálót
this(...)kell használniuk a generált konstruktorok egyikének (közvetlenül vagy közvetetten) delegálásához.
Az egyesítő konstruktorok típusok bármilyen típusúak lehetnek, amelyek például interfészekké object, típusparaméterekké, null értékű típusokká és más egyesítőkké alakulnak át. Az eredményként kapott esetek átfedése, valamint a egyesítések beágyazása vagy null értékre helyezése esetén is rendben van.
Példák:
// Union of existing types
public union Pet(Cat, Dog, Bird);
// Union with function member
public union OneOrMore<T>(T, IEnumerable<T>)
{
public IEnumerable<T> AsEnumerable() => Value switch
{
IEnumerable<T> list => list,
T value => [value],
}
}
// "Discriminated" union with freshly declared case types
public record class None();
public record class Some<T>(T value);
public union Option<T>(None, Some<T>);
#### Lowering
A union declaration is lowered to a struct declaration with
* the same attributes, modifiers, name, type parameters and constraints,
* implicit implementations of `IUnion`,
* a `public object? Value { get; }` auto-property,
* a public constructor for each *union constructor* type,
* any members in the union declaration's body.
It is an error for user-declared members to conflict with generated members.
Example:
``` c#
public union Pet(Cat, Dog){ ... }
A következőre van csökkentve:
[Union] public struct Pet : IUnion
{
public Pet(Cat value) => Value = value;
public Pet(Dog value) => Value = value;
public object? Value { get; }
... // original body
}
Kérdések megnyitása
[Megoldott] Rekord az unió-deklaráció?
Az egyesítő deklaráció rekordszerkezetre csökken
Úgy gondolom, hogy ez az alapértelmezett viselkedés szükségtelen, és mivel nem konfigurálható, jelentősen korlátozza a használati forgatókönyveket. A rekordok sok olyan kódot hoznak létre, amely vagy nem használt, vagy nem felel meg az adott követelményeknek. A rekordok például nagyjából tiltottak a fordító kódbázisában a kódblob miatt. Azt hiszem, hogy jobb lenne, ha megváltoztatná az alapértelmezett:
- Alapértelmezés szerint egy szakszervezeti deklaráció szabályos szerkezetet deklarál csak az unióspecifikus tagokkal.
- A felhasználó deklarálhat egy rekordegyesítőt:
record union U(E1, ...) ...
Felbontás: Az egyesítő deklaráció egyszerű szerkezet, nem rekordstruktúra. A record union ... nem támogatott
[Megoldott] Union deklaráció szintaxisa
Úgy tűnik, hogy a javasolt szintaxis hiányos vagy szükségtelenül korlátozott. Például úgy tűnik, hogy az alap záradék nem engedélyezett. Azonban könnyen el tudom képzelni, hogy szükség van egy interfész, például.
Azt hiszem, hogy eltekintve az elem-típusok-lista a szintaxisnak meg kell egyeznie a rendszeres struct/record struct deklaráció, ahol a struct kulcsszó helyébe union kulcsszó.
Felbontás: A korlátozás el lesz távolítva.
[Megoldott] Uniós deklaráció tagjai
A példánymezők, az automatikus tulajdonságok vagy a mezőszerű események nem engedélyezettek.
Ez önkényesnek és teljesen szükségtelennek tűnik.
Felbontás: A korlátozást megtartjuk.
[Megoldott] Null értékű értéktípusok uniós esettípusokként
Az egyesítő esettípusokat a konstruktorok paramétertípusainak készleteként azonosítja a rendszer. Az egyesítő esettípusokat a rendszer ezen gyári metódusok paramétertípusainak készleteként azonosítja.
Ugyanakkor:
Egy
TryGetValuemetódus minden esettípushoz. A metódus egy olyan típus egyszeri kimenő paraméterét adja visszabool, amely megfelel az adott esettípusnak a következő módon:
- Ha az esettípus null értékű, a paraméter típusának identitás-átalakíthatónak kell lennie az alapul szolgáló típusra
- Ellenkező esetben a típusnak identitás-átalakíthatónak kell lennie a kis- és nagybetűk típusára.
Van-e előnye annak, hogy az esettípusok között null értékű értéktípus szerepel, különösen, hogy egy típusminta nem használhat null értékű értéktípust céltípusként? Úgy tűnik, mintha egyszerűen azt mondanánk, hogy ha a konstruktor/gyár paramétertípusa null értékű, akkor a megfelelő esettípus az alapul szolgáló típus. Akkor nem lenne szükség a metódushoz szükséges extra záradékra, az TryGetValue összes kimenő paraméter esettípusok.
Felbontás: A javaslatot jóváhagyták
[Megoldott] A tulajdonság alapértelmezett null értékű állapota Value
Az olyan egyesítő típusok esetében, amelyeknél az esettípusok egyike sem null értékű, az alapértelmezett állapot
Valuea "nem null", nem pedig a "talán null".
Az új kialakításnál, ahol Value a tulajdonság nem valamilyen általános felületen van definiálva, de egy olyan API, amely kifejezetten a deklarált típushoz tartozik, a fent idézett szabály túltervezésnek tűnik. Ezenkívül a szabály valószínűleg arra kényszeríti a fogyasztókat, hogy null értékű típusokat használjanak olyan helyzetekben, amikor egyébként null értékű típusokat nem használnak.
Vegyük például a következő uniós deklarációt:
union U1(int, bool, DateTime);
Az idézett szabály szerint az alapértelmezett állapot Value "nem null". De ez nem egyezik a típus viselkedésével, default(U1).Value az.null A viselkedés átrendezéséhez a fogyasztónak legalább egy esettípust null értékűvé kell tennie. A következőhöz hasonló:
union U1(int?, bool, DateTime);
Ez azonban valószínűleg nem kívánatos, előfordulhat, hogy a fogyasztó nem szeretné engedélyezni az értékekkel való explicit létrehozást int? .
Javaslat: Távolítsa el az idézett szabályt, a null értékű elemzésnek széljegyzeteket kell használnia a tulajdonságból az Value alapértelmezett nullhiba megállapításához.
Felbontás: A javaslatot jóváhagyták
[Megoldott] Egyesítő értéktípus null értékű egyesítési egyezése
Ha egy minta bejövő értéke egyesítő típusú, az egyesítő érték tartalma a mintától függően "le van bontva".
Ki kell bontanunk ezt a szabályt olyan helyzetekre, amikor egy minta bejövő értéke egy Nullable<union type>?
Vegyük például a következő esetet:
static bool Test1(StructUnion? u)
{
return u is 1;
}
static bool Test2(ClassUnion? u)
{
return u is 1;
}
A Test1 és a Test2 jelentése u is 1 nagyon eltérő. A Test1-ben ez nem egyező egyezés, a 2. tesztben az.
Talán a "union matching" kell "dig" keresztül Nullable<T> mint minta egyeztetés általában nem más helyzetekben.
Ha ezzel megyünk, akkor az egyesítő minta nullNullable<union type> az osztályokhoz hasonlóan működik.
Azaz a minta igaz, ha (!nullableValue.HasValue || nullableValue.Value.Value is null).
Felbontás: A javaslatot jóváhagyták.
Mi a teendő a "rossz" API-kkal?
Mit kell tennie a fordítónak az egyezésnek tűnő, de egyébként "rossznak" tűnő, egyező API-khoz? A fordító például megkeresi a TryGetValue/HasValue azonosítót egyező aláírással, de ez "rossz", mert egy szükséges egyéni módosító, vagy ismeretlen funkciót igényel stb. A fordítónak csendben figyelmen kívül kell hagynia az API-t, vagy hibát kell jelentenie? Ehhez hasonlóan az API elavult/kísérletiként is megjelölhető. Jelentse a fordító a diagnosztikát, csendben használja az API-t, vagy csendben ne használja az API-t?
Mi a teendő, ha hiányoznak az egyesítő deklaráció típusai?
Mi történik, ha UnionAttributehiányzik IUnion vagy IUnion<TUnion> hiányzik? Hiba? Szintetizálása? Valami más?
[Megoldott] Általános IUnion-felület tervezése
Olyan argumentumok lettek megadva, amelyek IUnion<TUnion> nem örökölhetők a típusparamétertől IUnion , és nem korlátozhatók rá IUnion<TUnion>. Újra meg kell látogatnunk.
Felbontás: A IUnion<TUnion> rendszer egyelőre eltávolítja a felületet.
[Megoldott] Null értékű értéktípusok esettípusként és azok interakciója TryGetValue
A fenti szabályok azt felelnek meg, hogy ha egy esettípus null értékű, akkor a megfelelő TryGetValue metódusban használt paramétertípusnak kell lennie az alapul szolgáló típusnak.
Ezt az a tény motiválja, hogy ezen null a módszeren keresztül soha nem lehet értéket adni. A használati oldalon a null értékű értéktípus nem engedélyezett típusmintaként, míg az alapul szolgáló típussal való egyezésnek képesnek kell lennie a metódus hívására való leképezésre.
Meg kell erősítenünk, hogy egyetértünk ezzel a törléssel.
Felbontás: Megállapodott/megerősítve
A nem boxing union hozzáférési minta
Pontos szabályokat kell megadnia a megfelelő HasValue és TryGetValue API-k megtalálásához.
Öröklődésről van szó? Az olvasás/írás HasValue elfogadható egyezés? Stb.
[Megoldott] TryGetValue egyező konverziók
Az Unió egyeztetési szakasza a következőt mondja:
Egy olyan minta esetében, amely azt jelenti, hogy egy adott típust
Tkell ellenőrizni , ha elérhető egyTryGetValue(S value)metódus, és implicit módon áttérTaz értékreS, akkor a rendszer ezt a metódust használja az érték lekéréséhez.
Az implicit konverziók halmaza bármilyen módon korlátozva van? Engedélyezik például a felhasználó által definiált konverziókat? Mi a helyzet a tuple konverziókkal és más, nem annyira triviális átalakításokkal? Ezek némelyike még standard konverzió is.
A metódusok halmaza TryGetValue bármilyen más módon korlátozva van? Az Unió mintái szakasz például azt jelenti, hogy csak az esettípusnak megfelelő paramétertípusú metódusokat veszi figyelembe:
egy
public bool TryGetValue(out T value)metódus minden egyes esettípushozT.
Jó lenne, ha lenne egy explicit válasz.
Felbontás: Csak implicit identitások, hivatkozások vagy boxing konverziók tekinthetők
TryGetValue és null értékű elemzés
Ha a nem dobozoló hozzáférési minta
HasValueTryGetValue(...)egy egyesítési típus tartalmának lekérdezésére szolgál (explicit módon vagy mintaegyeztetéssel), az ugyanúgy befolyásoljaValuea nullképességi állapotot, minthaValueközvetlenül ellenőrizték volna: Az ág null állapotaValue"nem null"truelesz.
Bármilyen módon korlátozva van a metódusok készlete TryGetValue ? Az Unió mintái szakasz például azt jelenti, hogy csak az esettípusnak megfelelő paramétertípusú metódusokat veszi figyelembe:
egy
public bool TryGetValue(out T value)metódus minden egyes esettípushozT.
Jó lenne, ha lenne egy explicit válasz.
A strukturált egyesítési típusok értékeire vonatkozó default szabályok tisztázása
Megjegyzés: Az alább említett alapértelmezett érvénytelenségi szabály el lett távolítva.
Megjegyzés: Az alább említett "alapértelmezett" jól formázottsági szabályok el lettek távolítva. Meg kell erősítenünk, hogy ezt akarjuk.
A nullhiba szakasz a következőt mondja:
Az olyan egyesítő típusok esetében, amelyeknél az esettípusok egyike sem null értékű, az alapértelmezett állapot
Valuea "nem null", nem pedig a "talán null".
Tekintettel arra, hogy az alábbi példában a jelenlegi megvalósítás a "nem null" értéket veszi figyelembe Values2 :
S2 s2 = default;
struct S2 : System.Runtime.CompilerServices.IUnion
{
public S2(int x) => throw null!;
public S2(bool x) => throw null!;
object? System.Runtime.CompilerServices.IUnion.Value => throw null!;
}
Ugyanakkor a jól formázottság szakasz a következőt mondja:
- Alapértelmezett érték: Ha az egyesítő típus egy értéktípus, akkor az alapértelmezett érték
nullaz értékeValue.- Alapértelmezett konstruktor: Ha egy egyesítő típus nullary (no-argument) konstruktorsal rendelkezik, az eredményként kapott egyesítés
nulla magaValue.
Egy ilyen implementáció ellentmond a fenti példa null értékű elemzési viselkedésének.
Módosítani kell-e a jól formázottsági szabályokat, vagy "talán null" állapotúnak Valuedefault kell lennie?
Ha az utóbbi, az inicializálás S2 s2 = default; null értékű figyelmeztetést eredményez?
Győződjön meg arról, hogy egy típusparaméter soha nem egyesítő típus, még akkor sem, ha egyhez van korlátozva.
class C1 : System.Runtime.CompilerServices.IUnion
{
private readonly object _value;
public C1(int x) { _value = x; }
public C1(string x) { _value = x; }
object System.Runtime.CompilerServices.IUnion.Value => _value;
}
class Program
{
static bool Test1<T>(T u) where T : C1
{
return u is int; // Not a union matching
}
static bool Test2<T>(T u) where T : C1
{
return u is string; // Not a union matching
}
}
A feltétel utáni attribútumok befolyásolják-e az uniós példányok alapértelmezett nullképességét?
Megjegyzés: Az alább említett alapértelmezett érvénytelenségi szabály el lett távolítva. És a továbbiakban nem következtetünk a tulajdonság alapértelmezett érvénytelenségére az Value egyesítő létrehozási módszerekből. Ezért a kérdés elavult/már nem alkalmazható a jelenlegi kialakításra.
Az olyan egyesítő típusok esetében, amelyeknél az esettípusok egyike sem null értékű, az alapértelmezett állapot
Valuea "nem null", nem pedig a "talán null".
A következő forgatókönyvben várható a figyelmeztetés?
#nullable enable
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(int x) => throw null!;
public S1([System.Diagnostics.CodeAnalysis.NotNull] bool? x) => throw null!;
object? System.Runtime.CompilerServices.IUnion.Value => throw null!;
}
class Program
{
static void Test2(S1 s)
{
// warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
// For example, the pattern 'null' is not covered.
_ = s switch { int => 1, bool => 3 }; //
}
}
Uniós átalakítások
[Megoldott] Hová tartoznak a többi átalakítás között prioritás szempontjából?
Az egyesítő konverziók a felhasználó által definiált átalakítás egy másik formájának tűnnek. Ezért a jelenlegi implementáció közvetlenül azután sorolja be őket, hogy nem sikerült besorolni egy implicit felhasználó által definiált átalakítást, és létezés esetén a rendszer a felhasználó által definiált átalakításnak csak egy másik formájaként kezeli őket. Ennek a következő következményei vannak:
- Az implicit, felhasználó által definiált átalakítás elsőbbséget élvez az egyesítő átalakítással szemben
- Amikor explicit kódot használ a kódban, a felhasználó által definiált explicit átalakítás elsőbbséget élvez az egyesítő átalakítással szemben
- Ha nincs explicit kódbeosztás, az egyesítő átalakítás elsőbbséget élvez egy explicit, felhasználó által definiált átalakítással szemben
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(int x) => ...
public S1(string x) => ...
object System.Runtime.CompilerServices.IUnion.Value => ...
public static implicit operator S1(int x) => ...
}
struct S2 : System.Runtime.CompilerServices.IUnion
{
public S2(int x) => ...
public S2(string x) => ...
object System.Runtime.CompilerServices.IUnion.Value => ...
public static explicit operator S2(int x) => ...
}
class Program
{
static S1 Test1() => 10; // implicit operator S1(int x) is used
static S1 Test2() => (S1)20; // implicit operator S1(int x) is used
static S2 Test3() => 10; // Union conversion S2.S2(int) is used
static S2 Test4() => (S2)20; // explicit operator S2(int x)
}
Meg kell győződnie arról, hogy ez az a viselkedés, amit szeretünk. Ellenkező esetben az átváltási szabályokat tisztázni kell.
Felbontás:
A munkacsoport jóváhagyta.
[Megoldott] A konstruktor paraméterének ref-ness értéke
Jelenleg a nyelv csak a felhasználó által definiált konverziós operátorok számára engedélyezi a bájtértéket és in a paramétereket.
Úgy érzi, hogy ennek a korlátozásnak az okai az egyesítő átalakításokra alkalmas konstruktorokra is érvényesek.
Javaslat:
Módosítsa a case type constructorUnion types fenti szakasz definícióját:
-For each public constructor with exactly one parameter, the type of that parameter is considered a *case type* of the union type.
+For each public constructor with exactly one **by-value or `in`** parameter, the type of that parameter is considered a *case type* of the union type.
Felbontás:
A munkacsoport egyelőre jóváhagyja. Megfontolhatjuk azonban az esettípus konstruktorainak és az egyesítő típusú átalakításokhoz alkalmas konstruktorok készletének "felosztását".
[Megoldott] Null értékű konverziók
A Nullable Conversions (Nullable Conversions) szakasz kifejezetten felsorolja az alapul szolgáló konverziókat. A jelenlegi specifikáció nem javasol módosításokat a listához. Ez a következő forgatókönyvhöz tartozó hibát eredményez:
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(int x) => throw null;
public S1(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
class Program
{
static S1? Test1(int x)
{
return x; // error CS0029: Cannot implicitly convert type 'int' to 'S1?'
}
}
Javaslat:
Módosítsa a specifikációt úgy, hogy támogassa az implicit null értékű átalakítást ST? egy egyesítő konverzióról háttérre.
Pontosabban azt feltételezzük, hogy T egy egyesítő típus egy típusból vagy kifejezésből E implicit átalakítást T? eredményez, ha az egyesítő típusból E típusra C van konvertálva, és C esettípusaT.
Vegye figyelembe, hogy nem követelmény, hogy a típus E nem null értékű legyen.
Az átalakítást a rendszer az alapul szolgáló egyesítési átalakításként ST értékeli ki, amelyet egy sortörés T követ a T?
Felbontás:
Jóváhagyott.
[Megoldott] Emelt konverziók
Módosítani szeretnénk az Emelt konverziók szakaszt a feloldott egyesítő átalakítások támogatásához? Jelenleg nem engedélyezettek:
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(int x) => throw null;
public S1(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
class Program
{
static S1 Test1(int? x)
{
return x; // error CS0029: Cannot implicitly convert type 'int?' to 'S1'
}
static S1? Test2(int? y)
{
return y; // error CS0029: Cannot implicitly convert type 'int?' to 'S1?'
}
}
Felbontás:
Egyelőre nincsenek feloldott egyesítő átalakítások. Néhány megjegyzés a vitafórumból:
A felhasználó által definiált konverziók analógiái itt egy kicsit lebomlik. A szakszervezetek általában null értéket tartalmazhatnak, amely bejön. Nem egyértelmű, hogy az emelésnek létre kell-e hoznia egy egyesítő típusú
nullpéldányt a benne tárolt értékkel, vagy hogy létre kell-e hozniaNullable<Union>egynullértéket.
[Megoldott] Letiltja az egyesítő átalakítást egy alaptípusú példányból?
Az aktuális viselkedés zavarba ejtő lehet:
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(System.ValueType x)
{
}
public S1(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
class Program
{
static S1 Test1(System.ValueType x)
{
return x; // Union conversion
}
static S1 Test2(System.ValueType y)
{
return (S1)y; // Unboxing conversion
}
}
Megjegyzés: a nyelv kifejezetten tiltja a felhasználó által definiált konverziók alaptípusból való deklarálását. Ezért előfordulhat, hogy nem engedélyezi az egyesítő átalakításokat.
Felbontás:
Egyelőre semmi különlegeset nem csinál. Az általános forgatókönyvek egyébként sem védhetők teljesen.
[Megoldott] Letiltja az egyesítő átalakítást egy felülettípus egy példányából?
Az aktuális viselkedés zavarba ejtő lehet:
struct S1 : I1, System.Runtime.CompilerServices.IUnion
{
public S1(I1 x) => throw null;
public S1(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
interface I1 { }
struct S2 : System.Runtime.CompilerServices.IUnion
{
public S2(I1 x) => throw null;
public S2(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
class C3 : System.Runtime.CompilerServices.IUnion
{
public C3(I1 x) => throw null;
public C3(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
class Program
{
static S1 Test1(I1 x)
{
return x; // Union conversion
}
static S1 Test2(I1 x)
{
return (S1)x; // Unboxing
}
static S2 Test3(I1 x)
{
return x; // Union conversion
}
static S2 Test4(I1 x)
{
return (S2)x; // Union conversion
}
static C3 Test3(I1 x)
{
return x; // Union conversion
}
static C3 Test4(I1 x)
{
return (C3)x; // Reference conversion
}
}
Megjegyzés: a nyelv kifejezetten tiltja a felhasználó által definiált konverziók alaptípusból való deklarálását. Ezért előfordulhat, hogy nem engedélyezi az egyesítő átalakításokat.
Felbontás:
Egyelőre semmi különlegeset nem csinál. Az általános forgatókönyvek egyébként sem védhetők teljesen.
Az IUnion-felület névtere
Az illesztő névterét IUnion nem lehet megadni. Ha a szándék az, hogy egy névtérben global tartsa, akkor ezt explicit módon állítsuk be.
Javaslat: Ha ezt egyszerűen figyelmen kívül hagyják, használhatjuk System.Runtime.CompilerServices a névteret.
Osztályok típusként Union
[Megoldott] Példány ellenőrzése a következőhöz: null
Ha egy egyesítő típus egy osztálytípus, akkor az érték maga lehet null. Mi a helyzet akkor a null értékű ellenőrzésekkel?
A null minta közösen lett választva a Value tulajdonság ellenőrzésére, így hogyan ellenőrizheti, hogy maga az egyesítés nem null értékű-e?
Például:
- Amikor
SegyUnionstruktúra,s is nullegy értékS?csak akkor,truehasmaga .nullAmikorCegyUnionosztály, az érték azC?false,c is nullamikorcmaga,nullde akkor,truehacmaga nemnull, ésc.Valueaznull.
Egy másik példa:
class C1 : IUnion
{
private readonly object? _value;
public C1(){}
public C1(int x) { _value = x; }
public C1(string x) { _value = x; }
object? IUnion.Value => _value;
}
class Program
{
static int Test1(C1? u)
{
// warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
// For example, the pattern 'null' is not covered.
// This is very confusing, the switch expression is indeed not exhaustive (u itself is not
// checked for null), but there is a case 'null => 3' in the switch expression.
// It looks like the only way to shut off the warning is to use 'case _'. Adding it removes
// all benefits of exhaustiveness checking, any union case could be missing and there would
// be no diagnostic about that.
return u switch { int => 1, string => 2, null => 3 };
}
}
A kialakítás ezen része egyértelműen arra a várakozásra van optimalizálva, hogy az egyesítő típus egy szerkezet. Néhány lehetőség:
- Kár. A null értékű ellenőrzéshez használja
==a mintaegyezés helyett. - Hagyja, hogy a minta (és az
nullimplicit null-ellenőrzés más mintákban) az egyesítő értékre és annak tulajdonságáraValueegyaránt vonatkozzanak:u is null ==> u == null || u.Value == null. - Tiltsa le az osztályokat, hogy egyesítő típusok legyenek!
[Megoldott] Származtatás egy Union osztályból
Ha egy osztály egy osztályt Unionhasznál alaposztályként, az aktuális specifikációnak megfelelően maga az osztály lesz Union. Ez azért történik, mert automatikusan "örökli" az interfész implementálását IUnion , nem szükséges újra implementálni. Ugyanakkor a származtatott típus konstruktorai meghatározzák az új Uniontípuskészletet. A két osztály körül nagyon könnyű nagyon furcsa nyelvi viselkedést elérni:
class C1 : IUnion
{
private readonly object _value;
public C1(long x) { _value = x; }
public C1(string x) { _value = x; }
object IUnion.Value => _value;
}
class C2(int x) : C1(x);
class Program
{
static int Test1(C1 u)
{
// Good
return u switch { long => 1, string => 2, null => 3 };
}
static int Test2(C2 u)
{
// error CS8121: An expression of type 'C2' cannot be handled by a pattern of type 'long'.
// error CS8121: An expression of type 'C2' cannot be handled by a pattern of type 'string'.
return u switch { long => 1, string => 2, null => 3 };
}
}
Néhány lehetőség:
Akkor változik, ha egy osztálytípus típus
Union. Az osztály például akkorUniontípus, ha az összes igaz:- Ennek az az oka, hogy a
sealedszármaztatott típusok nem tekinthetők típusoknakUnion, ami zavaró. - Egyik alap sem implementál
IUnion
Ez még mindig nem tökéletes. A szabályok túl finomak. Könnyű hibázni. A deklaráción nincs diagnosztika, de
Unionaz egyeztetés nem működik.- Ennek az az oka, hogy a
Az osztályok nem lehetnek egyesítő típusúak.
[Megoldott] Az is-type operátor
Az is-type operátor futásidejű típusellenőrzésként van megadva. Szintaktikailag nagyon hasonlít egy típusmintára, de nem így van. Ezért a speciális Unionmegfeleltetés nem használható, ami zavart okozhat a felhasználók számára.
struct S1 : IUnion
{
private readonly object _value;
public S1(int x) { _value = x; }
public S1(string x) { _value = x; }
object IUnion.Value => _value;
}
class Program
{
static bool Test1(S1 u)
{
return u is int; // warning CS0184: The given expression is never of the provided ('int') type
}
static bool Test2(S1 u)
{
return u is string and ['1', .., '2']; // Good
}
}
Rekurzív egyesítés esetén előfordulhat, hogy a típusminta nem ad figyelmeztetést, de továbbra sem fogja megtenni, amit a felhasználó gondolhatna.
Felbontás: Típusmintaként kell működnie.
Listaminta
A listaminta mindig meghiúsul az egyezéssel Union :
struct S1 : IUnion
{
private readonly object _value;
public S1(int[] x) { _value = x; }
public S1(string[] x) { _value = x; }
object IUnion.Value => _value;
}
class Program
{
static bool Test1(S1 u)
{
// error CS8985: List patterns may not be used for a value of type 'object'. No suitable 'Length' or 'Count' property was found.
// error CS0021: Cannot apply indexing with [] to an expression of type 'object'
return u is [10];
}
}
static class Extensions
{
extension(object o)
{
public int Length => 0;
}
}
Egyéb kérdések
- A konstruktorok használata az egyesítő átalakításokban és az egyesítő mintaegyeztetésben való használata
TryGetValue(...)is megengedőnek van adva, ha több is alkalmaz: Csak egyet választanak. Ez nem számít a jól formázottsági szabályok szerint, de nem értünk hozzá? - A specifikáció részben a tulajdonság megvalósítására támaszkodik,
IUnion.Valuenem pedig az egyesítő típuson található bármelyValuetulajdonságra. Ennek célja, hogy nagyobb rugalmasságot biztosítson a meglévő típusok számára (amelyek más célokra sajátValuetulajdonnal rendelkezhetnek) a minta implementálásához. De ez kínos, és inkonzisztens, hogyan más tagok találhatók és használják közvetlenül a szakszervezet típusa. Változtassunk? Néhány egyéb lehetőség:- Egyesítő típusok megkövetelése egy nyilvános
Valuetulajdonság felfedéséhez. - Előnyben részesítsen egy nyilvános
Valuetulajdonságot, ha létezik, de ha nem (a szabályokhoz hasonlóan) térjen vissza aIUnion.ValuemegvalósításhozGetEnumerator.
- Egyesítő típusok megkövetelése egy nyilvános
- A javasolt egyesítő deklaráció szintaxisa nem általánosan kedvelt, különösen az esettípusok kifejezésekor. Az eddigi alternatívák is megfelelnek a kritikának, de lehetséges, hogy a végén változtatunk. Néhány, az aktuálisval kapcsolatos legfontosabb aggály:
- Úgy tűnhet, hogy a vesszők elválasztóként az esettípusok között azt jelentik, hogy a sorrend számít.
- A zárójeles listák túlságosan hasonlítanak az elsődleges konstruktorokhoz (annak ellenére, hogy nem rendelkeznek paraméternevekkel).
- Túl különbözik a számokat, amelyek a "esetek" a kapcsos zárójelek.
- Bár az egyesítő deklarációk egyetlen referenciamezővel hoznak létre szerkezeteket, az egyidejű környezetekben való használatkor még mindig kissé érzékenyek a váratlan viselkedésre. Ha például egy felhasználó által definiált függvénytag többször dereferens
this, akkor előfordulhat, hogy a benne lévő változót egy másik szál társította újra a két hozzáférés között. A fordító szükség esetén létrehozhat kódot egy helyi példányra történő másoláshozthis. Kellene? Általában milyen fokú egyidejűségi rugalmasság kívánatos és ésszerűen elérhető?
C# feature specifications