Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Problém šampiona: https://github.com/dotnet/csharplang/issues/9499
Shrnutí
Povolit deklarovat closedtřídu . Tím se zabrání deklarování přímo odvozených tříd v jiném sestavení:
// Assembly 1
public closed record class GateState;
public record class Closed : GateState;
public record class Open(float Percent) : GateState;
// Assembly 2
public record class Locked : GateState; // ERROR - 'GateState' is a closed class
Vzhledem k tomu, že všechny odvozené třídy jsou deklarovány v uzavřeném sestavení třídy, switch může být uzavřený výraz, který pokrývá všechny z nich, uzavřenou třídu uzavřenou - nemusí poskytovat výchozí případ, aby se zabránilo upozorněním.
// Assembly 3
GateState state = ...;
string description = state switch
{
Closed => "closed",
Open(var percent) => $"{percent}% open"
// No warning about missing cases
};
Motivation
Mnoho typů tříd není určeno k rozšíření kýmkoli, ale jejich autoři, ale jazyk neposkytuje žádný způsob, jak tento záměr vyjádřit, ať už se to děje. Pro příjemce třídy to znamená, že žádná sada odvozených tříd nebude považována za "vyčerpání" základní třídy a výraz přepínače musí obsahovat případ catch-all, aby se zabránilo upozorněním.
Uzavřené třídy poskytují způsob, jak označit, že sada odvozených tříd je dokončena a umožňuje využívání kódu spoléhat na to, aby byl vyčerpávající v výrazech switch.
Podrobný návrh
Syntaxe
Povolit closed jako modifikátor tříd.
closed Třída je implicitně abstraktní. Proto nemůže mít sealed ani static modifikátor.
Jedná se o chybu explicitně použití abstract modifikátoru closed ve třídě.
Třída odvozená z uzavřené třídy není sama o sobě uzavřena, pokud není výslovně deklarována jako
Omezení stejného sestavení
Pokud je třída v jednom sestavení deklarována closed , jedná se o chybu, která je přímo odvozena od třídy v jiném sestavení:
// Assembly 1
public closed class CC { ... }
public class CO : CC { ... } // Ok, same assembly
// Assembly 2
public class C1 : CC { ... } // Error, 'CC' is closed and in a different assembly
public class C2 : CO { ... } // Ok, 'CO' is not closed
Stejné omezení platí pro moduly. Podtyp closed typu musí být umístěn ve stejném modulu jako základní typ.
Omezení parametru typu
Pokud obecná třída je přímo odvozena od uzavřené třídy, musí být všechny její parametry typu použity ve specifikaci základní třídy:
closed class C<T> { ... }
class D1<U> : C<U> { ... } // Ok, 'U' is used in base class
class D2<V> : C<V[]> { ... } // Ok, 'V' is used in base class
class D3<W> : C<int> { ... } // Error, 'W' is not used in base class
Toto pravidlo zajistí, že existuje jedna obecná instance odvozeného typu, která "vyčerpá" danou obecnou instanci uzavřeného základního typu.
Poznámka: Toto pravidlo nemusí být dostatečné, pokud v určitém okamžiku povolíme uzavřená rozhraní, protože a) třídy mohou implementovat více obecných instancí stejného rozhraní a b) parametry typu rozhraní mohou být společné nebo kontravariantní. V takovém okamžiku budeme muset pravidlo upřesnit, aby se zajistilo, že existuje pouze jedna obecná instance daného odvozeného typu na obecnou instanci uzavřeného základního typu.
Úplnost přepínačů
Výraz switch , který zpracovává všechny přímé potomky uzavřené třídy, se považuje za vyčerpání této třídy. To znamená, že se už nebudou zobrazovat některá upozornění, která nejsou vyčerpávající:
CC cc = ...;
_ = cc switch
{
CO co => ...,
// No warning about non-exhaustive switch
};
Na druhou stranu to také znamená, že může být chybou, že uzavřená základní třída může nastat jako případ po všech jeho přímých potomcích:
_ = cc switch
{
CO co => ...,
CC cc => ..., // Error, case cannot be reached
};
Poznámka: Pro určité obecné instance uzavřené základní třídy nemusí existovat platné odvozené třídy. Úplný přepínač musí určovat pouze případy pro odvozené typy, které jsou skutečně možné.
Příklad:
closed class C<T> { ... }
class D1<U> : C<U> { ... }
class D2<V> : C<V[]> { ... }
Například C<string>neexistuje žádná odpovídající instance D2<...>a v přepínači není třeba dát žádný případ D2<...> :
C<string> cs = ...;
_ = cs switch
{
D1<string> d1 => ...,
// No need for a 'D2<...>' case - no instantiation corresponds to 'C<string>'
}
Úplnost, pokud podtyp nejde použít
Pokud podtyp není platný na konkrétním webu použití kvůli porušením omezení, porušením přístupnosti nebo jiným důvodům, pak není možné přepínač vyčerpat podtypy.
closed class C;
class D1 : C;
class Container
{
protected class D2 : C;
}
class Program
{
int M(C c)
=> c switch
{
D1 => 1,
// warning: switch is non-exhaustive. Pattern 'C' is not handled.
};
}
To platí také v případě, že obecný podtyp není přečístelný a jeho použitelnost může záviset na konečné nahrazení argumentu typu.
closed class C<T> { ... }
class D1<U> : C<U> { ... }
class D2<V> : C<V[]> { ... }
class Program
{
int M<X>(C<X> c)
=> c switch
{
D1<X> => 1,
// warning: switch is non-exhaustive. Pattern 'C' is not handled.
};
}
Omezení podtypů neovlivňují úplnost.
Jazyk nezpřesňuje určení, zda je možné podtyp na základě omezení parametrů typu v definici základního typu a podtypu.
closed class C<T>;
class D1<U1> : C<U1>;
class D2<U2> : C<U2> where U2 : struct;
class Program
{
int M1<X>(C<X> c) where X : class
{
// warning: switch is not exhaustive. Pattern 'C<X>' is not handled.
return c switch
{
D1<X> => 1,
};
}
int M2<X>(C<X> c) where X : class
{
return c switch
{
D1<X> => 1,
C<X> => 2, // ok
};
}
}
Například výše uvedené výrazy přepínače, neanalyzovat konstrukce D2<X>přesně tak, aby si uvědomil, že všechny možné X porušení omezení U2. Proto předpokládá, že některé D2<X> je možné, a požádá uživatele, aby ho zpracovávalo vyčerpáním základního typu.
Úplnost v případě, že neexistují žádné podtypy
Pokud uzavřená třída nemá žádné podtypy, prázdný přepínač není považován za vyčerpávající.
Poznámky: Předpokládá se, že se jedná o "přechodný stav" v normálním kódu. Autor pravděpodobně provede změnu deklarace podtypu v tomto scénáři. Toto chování se rovná "quirk" (bez ohledu na to, že se zpracovává všech 0 podtypů), jazyk stále žádá uživatele, aby ošetřoval základní typ.
closed class C;
class Program
{
int M1(C c)
// warning: switch is not exhaustive.
=> c switch
{
};
int M2(C c)
=> c switch
{
C => 1, // ok
};
}
Úplnost parametrů typu omezených na uzavřený typ
Parametr typu omezený na uzavřenou třídu je považován za uzavřenou třídu pro účely vyčerpávající kontroly úplnosti.
closed class C;
class D1 : C;
class D2 : C;
class Program
{
int M1<X>(X x) where X : C
=> x switch
{
D1 => 1,
D2 => 2,
};
int M2<X>(X x) where X : C
=> x switch
{
D1 => 1,
D2 => 2,
C => 3, // error: 'C' is subsumed by the previous cases
};
}
Určení podtypů uzavřené třídy
Úplnost přepínačů nad uzavřenými typy tříd je určena kontrolou, zda je přepínač vyčerpávající nad sadou podtypů vstupního uzavřeného typu třídy.
Sada podtypů S uzavřené třídy je určena následujícím způsobem:
- U daného uzavřeného typu
CnechteC₀původní definici. - Pro každou deklaraci
S₀podtypu, jejíž základní typ má původní definiciC₀, určete, zda existuje konstrukceS, která má základní typC.- Viz také §19.6.3 Jedinečnost implementovaných rozhraní ve standardu.
- Pokud takový typ
Sexistuje, je součástí sady podtypů.
Převod rozhraní uzavřených tříd
Uzavřená třída má zapečetěnou hierarchii, pokud jsou všechny její podtypy buď zapečetěné , nebo mají zapečetěnou hierarchii. To znamená, že všechny třídy v rozbalené hierarchii jsou buď zapečetěné nebo uzavřené.
Pokud má uzavřená třída zapečetěnou hierarchii, zavádí se omezení převodnosti rozhraní . Tím zabráníte pokusu o převod na typ rozhraní, který by nikdy nemohl být úspěšný.
Toto omezení je podobné jako explicitní převod odkazu z zapečetěného typu třídy na typ rozhraní. Viz §10.3.5 Explicitní převody odkazů.
var c = new C();
var i = (I)c; // error
closed class C { }
sealed class D1 : C { }
sealed class D2 : C { }
interface I { }
Určíme, jestli explicitní převod odkazu z C existující I existuje rekurzivně shromažďováním sady rozhraní implementovaných podle C podtypů a jeho podtypů. Pokud sada rozhraní zahrnuje Ia neimplementuje I, pak explicitní odkaz převod existuje z C .IC (V případě, že C implementuje I, je místo toho k dispozici implicitní převod odkazu.)
Snížení
Uzavřené třídy se generují s atributem IsClosedType , aby je bylo možné rozpoznat pomocí náročného kompilátoru.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class IsClosedTypeAttribute : Attribute { }
}
Blokování podtypů z jiných jazyků nebo kompilátorů
Uzavřené třídy se nezdědí z jazyků, které nepodporují uzavřené třídy. Toho se dosahuje přidáním [CompilerFeatureRequired("ClosedClasses")] všech konstruktorů uzavřených tříd.
// Authoring assembly, built with .NET 10 SDK
closed class C1
{
public C1() { }
public C1(int param) { }
}
// Consuming assembly, built with .NET 8 SDK
class C2 : C1
{
public C2() { } // error: 'C1.C1()' requires compiler feature "ClosedClasses"
public C2() : base(42) { } // error: 'C1.C1(int)' requires compiler feature "ClosedClasses"
}
Metadata "view" of C1:
[IsClosedType]
class C1
{
[CompilerFeatureRequired("ClosedClasses")]
public C1() { }
[CompilerFeatureRequired("ClosedClasses")]
public C1(int param) { }
}
Všimněte si, že na rozdíl od funkce "požadované členy" se kromě atributu CompilerFeatureRequiredAttribute nevygeneruje zastaralá položka. Vygeneruje se pouze ten druhý.
Multiple CompilerFeatureRequiredAttributes
V následujícím scénáři kompilátor vygeneruje samostatnou CompilerFeatureRequired, pro každou požadovanou funkci, která je relevantní pro symbol:
closed class C1
{
public C() { }
public required string P { get; set; }
}
// Metadata:
class C1
{
[Obsolete("Types with required members are not supported in this version of your compiler")]
[CompilerFeatureRequired("RequiredMembers")]
[CompilerFeatureRequired("ClosedClasses")]
public C1() { }
}
Nevýhody
- Může se jednat o zásadní změnu přidání modifikátoru
closeddo existující třídy nebo přidání další odvozené třídy z uzavřené třídy. Před publikováním uzavřené třídy musí autor zvážit dlouhodobý kontrakt, který s jeho spotřebiteli znamená.
Alternativy
- Místo nového
closedmodifikátoru může být uzavřená třída označena atributem[Closed]. - Rozsah povolených potomků lze dále zúžit na soubor (i když by neměl v jazyce C#mnoho předchůdců) nebo do těla uzavřené třídy jako vnořené třídy.
- Uzavřená sada povolených potomků by mohla být uvedena jako seznam místo toho, kde dochází k deklaracím. To by umožnilo zahrnutí tříd do jiných sestavení.
Volitelné funkce
- Rozhraní můžou být také uzavřena. Pravidla by byla velmi podobná.
Otevřené otázky
N/A
C# feature specifications