Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Note
Ten artykuł jest specyfikacją funkcji. Specyfikacja służy jako dokument projektowy dla funkcji. Zawiera proponowane zmiany specyfikacji wraz z informacjami wymaganymi podczas projektowania i opracowywania funkcji. Te artykuły są publikowane do momentu sfinalizowania proponowanych zmian specyfikacji i włączenia ich do obecnej specyfikacji ECMA.
Mogą wystąpić pewne rozbieżności między specyfikacją funkcji a ukończoną implementacją. Te różnice są zawarte w odpowiednich notatkach ze spotkania dotyczącego projektowania języka (LDM).
Więcej informacji na temat procesu wdrażania specyfikacji funkcji można znaleźć w standardzie języka C# w artykule dotyczącym specyfikacji .
Kwestia dotycząca mistrza: https://github.com/dotnet/csharplang/issues/9499
Podsumowanie
Zezwalaj na deklarowanie closedklasy . Zapobiega to deklarowaniu bezpośrednio pochodnych klas w innym zestawie:
// 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
Ponieważ wszystkie klasy pochodne są deklarowane w zestawie zamkniętej klasy, wyrażenie zużywające switch , które obejmuje wszystkie z nich, można zakończyć do "wyczerpania" zamkniętej klasy - nie musi podawać domyślnego przypadku, aby uniknąć ostrzeżeń.
// Assembly 3
GateState state = ...;
string description = state switch
{
Closed => "closed",
Open(var percent) => $"{percent}% open"
// No warning about missing cases
};
Motywacja
Wiele typów klas nie ma być rozszerzonych przez nikogo, ale ich autorów, ale język nie zapewnia możliwości wyrażenia tej intencji, nie mówiąc już o tym, aby się temu sprzeciwiać. Dla konsumentów klasy oznacza to, że żaden zestaw klas pochodnych nie zostanie uznany za "wyczerpany" klasę bazową, a wyrażenie przełącznika musi zawierać przypadek catch-all, aby uniknąć ostrzeżeń.
Zamknięte klasy zapewniają sposób wskazywania, że zestaw klas pochodnych jest kompletny i umożliwia korzystanie z kodu, aby polegać na tym w celu wyczerpującości w wyrażeniach przełącznika.
Szczegółowy projekt
Syntax
Zezwalaj closed jako modyfikator klas. Klasa closed jest niejawnie abstrakcyjna. W związku z tym nie może również mieć sealed modyfikatora ani static .
Jest to błąd jawnego użycia abstract modyfikatora w closed klasie.
Klasa pochodząca z zamkniętej klasy nie jest zamknięta, chyba że jawnie zadeklarowana jest.
Ograniczenie dotyczące tego samego zestawu
Jeśli klasa w jednym zestawie jest zadeklarowana closed , jest to błąd, aby bezpośrednio z niego pochodzić w innym zestawie:
// 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
To samo ograniczenie dotyczy modułów. Podtyp closed typu musi znajdować się w tym samym module co typ podstawowy.
Ograniczenie parametru typu
Jeśli klasa ogólna pochodzi bezpośrednio z zamkniętej klasy, wszystkie jej parametry typu muszą być używane w specyfikacji klasy bazowej:
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
Ta reguła polega na upewnieniu się, że istnieje pojedyncze ogólne wystąpienie typu pochodnego, który "wyczerpuje" daną ogólną wystąpienia typu bazy zamkniętej.
Uwaga: Ta reguła może nie być wystarczająca, jeśli w pewnym momencie zezwalamy na zamknięte interfejsy, ponieważ a) klasy mogą implementować wiele ogólnych wystąpień tego samego interfejsu, a b) parametry typu interfejsu mogą być współ- lub kontrawariantne. W takim momencie musimy uściślić regułę, aby nadal mieć pewność, że istnieje tylko jedno wystąpienie ogólne danego typu pochodnego na wystąpienie typu zamkniętego podstawowego.
Wyczerpującość przełączników
Wyrażenie switch , które obsługuje wszystkie bezpośrednie elementy potomne klasy zamkniętej, zostanie uznane za wyczerpane tej klasy. Oznacza to, że niektóre ostrzeżenia o braku wyczerpującości nie będą już wyświetlane:
CC cc = ...;
_ = cc switch
{
CO co => ...,
// No warning about non-exhaustive switch
};
Z drugiej strony oznacza to również, że może to być błąd, aby zamknięta klasa bazowa występowała jako przypadek po wszystkich jej bezpośrednich elementach potomnych:
_ = cc switch
{
CO co => ...,
CC cc => ..., // Error, case cannot be reached
};
Uwaga: Dla niektórych wystąpień ogólnych zamkniętej klasy bazowej może nie istnieć prawidłowe klasy pochodne. Wyczerpujący przełącznik musi określać tylko przypadki dla typów pochodnych, które są rzeczywiście możliwe.
Przykład:
closed class C<T> { ... }
class D1<U> : C<U> { ... }
class D2<V> : C<V[]> { ... }
Na C<string>przykład w przełączniku nie ma odpowiedniego D2<...>wystąpienia elementu , a w przełączniku nie ma żadnego przypadku D2<...> :
C<string> cs = ...;
_ = cs switch
{
D1<string> d1 => ...,
// No need for a 'D2<...>' case - no instantiation corresponds to 'C<string>'
}
Wyczerpującość, gdy nie można użyć podtypu
Jeśli podtyp nie jest prawidłowy w określonej witrynie użycia, ze względu na naruszenia ograniczeń, naruszenia ułatwień dostępu lub inne przyczyny, nie można wyczerpać przełącznika za pośrednictwem podtypów.
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.
};
}
Dotyczy to również sytuacji, gdy podtyp ogólny nie jest czytelny, a jego zastosowanie może zależeć od podstawienia argumentu końcowego 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.
};
}
Ograniczenia podtypu nie mają wpływu na wyczerpującość
Język nie uściśli określenia, czy podtyp jest możliwy na podstawie ograniczeń dotyczących parametrów typu w definicji typu podstawowego i 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
};
}
}
Na przykład powyższe wyrażenia przełącznika nie analizują D2<X>konstrukcji wystarczająco dokładnie, aby zdać sobie sprawę, że wszystkie możliwe X naruszenia ograniczeń .U2 W związku z tym zakłada się, że niektóre z nich D2<X> są możliwe i prosi użytkownika o jego obsługę przez wyczerpanie typu podstawowego.
Wyczerpującość, gdy nie istnieją żadne podtypy
Gdy zamknięta klasa nie ma podtypów, pusty przełącznik nie jest traktowany jako wyczerpujący.
Uwagi: Przyjmuje się, że jest to "stan pośredni" w normalnym kodzie. Autor najprawdopodobniej wprowadzi zmianę w celu zadeklarowania podtypu w tym scenariuszu. To zachowanie jest "dziwaczne"- pomimo "wszystkich obsługiwanych podtypów 0", język nadal prosi użytkownika o obsługę typu podstawowego.
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
};
}
Wyczerpującość parametrów typu ograniczonych do typu zamkniętego
Parametr typu ograniczony do zamkniętej klasy jest traktowany podobnie jak zamknięta klasa do celów wyczerpujących kontroli.
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
};
}
Określanie podtypów zamkniętej klasy
Wyczerpującość przełączników dla zamkniętych typów klas jest określana przez sprawdzenie, czy przełącznik jest wyczerpujący w zestawie podtypów typu zamkniętej klasy wejściowej.
Zestaw podtypów S zamkniętej klasy jest określany w następujący sposób:
- Dla danego zamkniętego typu
CniechC₀będzie oryginalną definicją. - Dla każdej deklaracji podtypu
S₀, której typ podstawowy ma oryginalną definicjęC₀, ustal, czy konstrukcjaSistnieje, która ma typCpodstawowy .- Zobacz też §19.6.3 Unikatowość implementowanych interfejsów w standardzie.
- Jeśli taki element
Sistnieje, znajduje się w zestawie podtypów.
Konwertowanie interfejsu zamkniętych klas
Mówi się, że zamknięta klasa ma zapieczętowaną hierarchię, jeśli wszystkie jego podtypy są zapieczętowane lub mają zapieczętowaną hierarchię. Oznacza to, że wszystkie klasy w rozszerzonej hierarchii są zapieczętowane lub zamknięte.
Gdy zamknięta klasa ma zapieczętowaną hierarchię, zostanie wprowadzone ograniczenie konwersji interfejsu . Zapobiega to próbie konwersji na typ interfejsu, co nigdy nie mogło się powieść.
To ograniczenie jest podobne do jawnej konwersji odwołania z zapieczętowanego typu klasy do typu interfejsu. Zobacz §10.3.5 Jawne konwersje odwołań.
var c = new C();
var i = (I)c; // error
closed class C { }
sealed class D1 : C { }
sealed class D2 : C { }
interface I { }
Określamy, czy jawna konwersja odwołania z C do I istnieje, rekursywnie zbierając zestaw interfejsów implementowanych przez C i jego podtypy. Jeśli zestaw interfejsów zawiera Ielement i C nie implementuje Imetody , jawna konwersja odwołania istnieje z C do I. (W przypadku, gdy C implementuje Ielement , zamiast tego dostępna jest niejawna konwersja odwołania).
Obniżenie
Zamknięte klasy są generowane za pomocą atrybutu IsClosedType , aby umożliwić ich rozpoznawanie przez kompilator zużywające.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class IsClosedTypeAttribute : Attribute { }
}
Blokowanie podtypowania z innych języków/kompilatorów
Zamknięte klasy nie są dziedziczone z języków, które nie obsługują zamkniętych klas. Jest to realizowane przez dodanie [CompilerFeatureRequired("ClosedClasses")] do wszystkich konstruktorów zamkniętych klas.
// 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"
}
Metadane "widok" elementu C1:
[IsClosedType]
class C1
{
[CompilerFeatureRequired("ClosedClasses")]
public C1() { }
[CompilerFeatureRequired("ClosedClasses")]
public C1(int param) { }
}
Należy pamiętać, że w przeciwieństwie do funkcji "wymaganych elementów członkowskich" element ObsoleteAttribute nie jest emitowany oprócz atrybutu CompilerFeatureRequiredAttribute. Emitowane są tylko te ostatnie.
Wiele atrybutów CompilerFeatureRequiredAttributes
W scenariuszu takim jak poniżej kompilator emituje oddzielną CompilerFeatureRequiredfunkcję , dla każdej wymaganej funkcji, która jest odpowiednia dla symbolu:
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() { }
}
Złe strony
- Może to być zmiana powodująca niezgodność, aby dodać
closedmodyfikator do istniejącej klasy lub dodać dodatkową klasę pochodną z zamkniętej klasy. Przed opublikowaniem zamkniętej klasy autor musi wziąć pod uwagę długoterminową umowę, która implikuje ze swoimi konsumentami.
Alternatywy
- Zamiast nowego
closedmodyfikatora można wyznaczyć zamkniętą klasę za pomocą atrybutu[Closed]. - Zakres, w którym obiekty potomne są dozwolone, można zawęzić dalej do pliku (chociaż nie miałoby to wielu precedensów w języku C#) lub wewnątrz treści zamkniętej klasy jako klas zagnieżdżonych.
- Zamknięty zestaw dozwolonych elementów potomnych można podać jako listę zamiast sugerować, gdzie występują deklaracje. Pozwoliłoby to na włączenie klas w innych zestawach.
Funkcje opcjonalne
- Interfejsy mogą być również zamykane. Reguły byłyby bardzo podobne.
Otwórz pytania
N/A
C# feature specifications