Zmienianie reguł zgodności
W całej historii platforma .NET próbowała zachować wysoki poziom zgodności z wersji na wersję i we wszystkich implementacjach platformy .NET. Mimo że platformy .NET 5 (i .NET Core) i nowsze wersje można traktować jako nową technologię w porównaniu z programem .NET Framework, dwa główne czynniki ograniczają możliwość tej implementacji platformy .NET, aby odbiegać od platformy .NET Framework:
Duża liczba deweloperów, którzy pierwotnie opracowali lub nadal opracowywali aplikacje .NET Framework. Oczekują one spójnego zachowania w implementacjach platformy .NET.
Projekty bibliotek platformy .NET Standard umożliwiają deweloperom tworzenie bibliotek przeznaczonych dla typowych interfejsów API udostępnianych przez programy .NET Framework i .NET 5 (i .NET Core) oraz nowsze wersje. Deweloperzy oczekują, że biblioteka używana w aplikacji .NET 5 powinna zachowywać się identycznie z tą samą biblioteką używaną w aplikacji .NET Framework.
Oprócz zgodności w implementacjach platformy .NET deweloperzy oczekują wysokiego poziomu zgodności w różnych wersjach danej implementacji platformy .NET. W szczególności kod napisany dla starszej wersji platformy .NET Core powinien działać bezproblemowo na platformie .NET 5 lub nowszej wersji. W rzeczywistości wielu deweloperów oczekuje, że nowe interfejsy API znalezione w nowo wydanych wersjach platformy .NET powinny być również zgodne z wersjami wstępnymi, w których wprowadzono te interfejsy API.
W tym artykule opisano zmiany wpływające na zgodność i sposób, w jaki zespół platformy .NET ocenia każdy typ zmiany. Zrozumienie, w jaki sposób zespół platformy .NET zbliża się do możliwych zmian powodujących niezgodność, jest szczególnie przydatne dla deweloperów, którzy otwierają żądania ściągnięcia modyfikujące zachowanie istniejących interfejsów API platformy .NET.
W poniższych sekcjach opisano kategorie zmian wprowadzonych w interfejsach API platformy .NET oraz ich wpływ na zgodność aplikacji. Zmiany są dozwolone (), niedozwolone (✔️❌) lub wymagają osądu oraz oceny, jak przewidywalne, oczywiste i spójne było poprzednie zachowanie (❓).
Uwaga
- Oprócz obsługi jako przewodnika po sposobie oceniania zmian bibliotek platformy .NET deweloperzy bibliotek mogą również użyć tych kryteriów do oceny zmian w bibliotekach przeznaczonych dla wielu implementacji i wersji platformy .NET.
- Aby uzyskać informacje na temat kategorii zgodności, na przykład zgodności z poprzednimi wersjami i do przodu, zobacz Jak zmiany kodu mogą mieć wpływ na zgodność.
Modyfikacje umowy publicznej
Zmiany w tej kategorii modyfikują obszar powierzchni publicznej typu. Większość zmian w tej kategorii jest niedozwolona, ponieważ naruszają one zgodność z poprzednimi wersjami (możliwość aplikacji opracowanej przy użyciu poprzedniej wersji interfejsu API do wykonania bez ponownej kompilacji w nowszej wersji).
Typy
✔️ DOZWOLONE: Usuwanie implementacji interfejsu z typu, gdy interfejs jest już implementowany przez typ podstawowy
❓ WYMAGA OSĄDU: Dodawanie nowej implementacji interfejsu do typu
Jest to akceptowalna zmiana, ponieważ nie ma ona negatywnego wpływu na istniejących klientów. Wszelkie zmiany typu muszą działać w granicach dopuszczalnych zmian zdefiniowanych tutaj, aby nowa implementacja pozostała akceptowalna. Skrajna ostrożność jest niezbędna w przypadku dodawania interfejsów, które bezpośrednio wpływają na zdolność projektanta lub serializatora do generowania kodu lub danych, których nie można używać na poziomie obniżonym. Przykładem jest ISerializable interfejs.
❓ WYMAGA OSĄDU: Wprowadzenie nowej klasy bazowej
Typ można wprowadzić do hierarchii między dwoma istniejącymi typami, jeśli nie wprowadza żadnych nowych abstrakcyjnych elementów członkowskich lub zmienia semantyka lub zachowanie istniejących typów. Na przykład w programie .NET Framework 2.0 DbConnection klasa stała się nową klasą bazową dla SqlConnectionprogramu , która wcześniej pochodziła bezpośrednio z Componentklasy .
✔️ DOZWOLONE: Przenoszenie typu z jednego zestawu do innego
Stary zestaw musi być oznaczony elementem TypeForwardedToAttribute wskazującym nowy zestaw.
✔️ DOZWOLONE: zmiana typu struktury na
readonly struct
typreadonly struct
Zmiana typu nastruct
typ jest niedozwolona.✔️ DOZWOLONE: dodawanie zapieczętowanego lub abstrakcyjnego słowa kluczowego do typu, gdy nie ma dostępnych (publicznych lub chronionych) konstruktorów
✔️ DOZWOLONE: rozszerzanie widoczności typu
❌NIEDOZWOLONE: zmiana przestrzeni nazw lub nazwy typu
❌NIEDOZWOLONE: zmiana nazwy lub usunięcie typu publicznego
Spowoduje to przerwanie wszystkich kodu, który używa zmienionego lub usuniętego typu.
Uwaga
W rzadkich przypadkach platforma .NET może usunąć publiczny interfejs API. Aby uzyskać więcej informacji, zobacz Usuwanie interfejsu API na platformie .NET. Aby uzyskać informacje o . Zasady pomocy technicznej platformy .NET można znaleźć w temacie Zasady pomocy technicznej platformy .NET.
❌NIEDOZWOLONE: Zmiana bazowego typu wyliczenia
Jest to zmiana powodująca niezgodność w czasie kompilacji i zachowanie, a także binarną zmianę powodującą niezgodność, która może sprawić, że argumenty atrybutów będą nierozłączelne.
❌NIEDOZWOLONE: Uszczelnianie typu, który był wcześniej niezauczętowany
❌NIEDOZWOLONE: Dodawanie interfejsu do zestawu podstawowych typów interfejsu
Jeśli interfejs implementuje interfejs, który wcześniej nie zaimplementował, wszystkie typy implementujące oryginalną wersję interfejsu są uszkodzone.
❓ WYMAGA OSĄDU: Usunięcie klasy z zestawu klas bazowych lub interfejsu z zestawu zaimplementowanych interfejsów
Istnieje jeden wyjątek od reguły usuwania interfejsu: można dodać implementację interfejsu pochodzącego z usuniętego interfejsu. Na przykład można usunąćIDisposable, jeśli typ lub interfejs implementuje teraz element , który implementuje IComponentIDisposableelement .
❌DISALLOWED: zmiana
readonly struct
typu na typ strukturyJednak zmiana
struct
typu nareadonly struct
typ jest dozwolona.❌DISALLOWED: zmiana typu struktury na
ref struct
typ i odwrotnie❌NIEDOZWOLONE: zmniejszenie widoczności typu
Jednak zwiększenie widoczności typu jest dozwolone.
Elementy członkowskie
✔️ DOZWOLONE: rozszerzanie widoczności elementu członkowskiego, który nie jest wirtualny
✔️ DOZWOLONE: dodawanie abstrakcyjnego elementu członkowskiego do typu publicznego, który nie ma dostępnych (publicznych lub chronionych) konstruktorów lub typ jest zapieczętowany
Jednak dodanie abstrakcyjnego elementu członkowskiego do typu, który ma dostępne (publiczne lub chronione) konstruktory i nie
sealed
jest dozwolony.✔️ DOZWOLONE: Ograniczanie widoczności chronionego elementu członkowskiego, gdy typ nie ma dostępnych (publicznych lub chronionych) konstruktorów lub typ jest zapieczętowany
✔️ DOZWOLONE: przeniesienie elementu członkowskiego do klasy wyższej w hierarchii niż typ, z którego został usunięty
✔️ DOZWOLONE: dodawanie lub usuwanie przesłonięcia
Wprowadzenie przesłonięcia może spowodować, że poprzedni użytkownicy przesłonią przesłonięcie podczas wywoływania bazy.
✔️ DOZWOLONE: Dodanie konstruktora do klasy wraz z konstruktorem bez parametrów, jeśli klasa wcześniej nie miała konstruktorów
Jednak dodanie konstruktora do klasy, która wcześniej nie miała konstruktorów bez dodawania konstruktora bez parametrów, jest niedozwolone.
✔️ DOZWOLONE: zmiana elementu członkowskiego z abstrakcji na wirtualną
✔️ DOZWOLONE: zmiana wartości z na
ref readonly
zwracanąref
(z wyjątkiem metod wirtualnych lub interfejsów)✔️ DOZWOLONE: usuwanie odczytu z pola, chyba że statyczny typ pola jest modyfikowalnym typem wartości
✔️ DOZWOLONE: wywoływanie nowego zdarzenia, które nie zostało wcześniej zdefiniowane
❓ WYMAGA OSĄDU: Dodawanie nowego pola wystąpienia do typu
Ta zmiana ma wpływ na serializacji.
❌NIEDOZWOLONE: zmiana nazwy lub usunięcie publicznego elementu członkowskiego lub parametru
Spowoduje to przerwanie wszystkich kodu, który używa zmienionej nazwy lub usuniętego elementu członkowskiego lub parametru.
Obejmuje to usuwanie lub zmienianie nazwy elementu pobierającego lub ustawiającego z właściwości, a także zmienianie nazw lub usuwanie elementów członkowskich wyliczenia.
❌NIEDOZWOLONE: Dodawanie elementu członkowskiego do interfejsu
Jeśli podasz implementację, dodanie nowego elementu członkowskiego do istniejącego interfejsu niekoniecznie spowoduje błędy kompilacji w zestawach podrzędnych. Jednak nie wszystkie języki obsługują domyślne elementy członkowskie interfejsu (DIM). Ponadto w niektórych scenariuszach środowisko uruchomieniowe nie może zdecydować, który domyślny element członkowski interfejsu ma być wywoływany. Z tych powodów dodanie elementu członkowskiego do istniejącego interfejsu jest uznawane za zmianę powodującą niezgodność.
❌DISALLOWED: Zmiana wartości elementu członkowskiego stałej publicznej lub wyliczenia
❌NIEDOZWOLONE: Zmiana typu właściwości, pola, parametru lub wartości zwracanej
❌NIEDOZWOLONE: Dodawanie, usuwanie lub zmienianie kolejności parametrów
❌NIEDOZWOLONE: Dodawanie lub usuwanie słowa kluczowego in, out lub ref z parametru
❌DISALLOWED: zmiana nazwy parametru (w tym zmiana wielkości liter)
Jest to uznawane za niezgodność z dwóch powodów:
Przerywa ona scenariusze związane z późnym opóźnieniem, takie jak funkcja późnego powiązania w Visual Basic i dynamiczne w języku C#.
Przerywa zgodność źródła, gdy deweloperzy używają nazwanych argumentów.
❌DISALLOWED: Zmiana wartości zwracanej
ref
na wartość zwracanąref readonly
❌✔ NIEDOZWOLONE: Zmiana wartości z wartości na
ref readonly
zwracanąref
w metodzie wirtualnej lub interfejsie❌NIEDOZWOLONE: Dodawanie lub usuwanie abstrakcji z elementu członkowskiego
❌DISALLOWED: Usuwanie wirtualnego słowa kluczowego z elementu członkowskiego
❌DISALLOWED: Dodawanie wirtualnego słowa kluczowego do elementu członkowskiego
Chociaż często nie jest to zmiana powodująca niezgodność, ponieważ kompilator języka C# ma tendencję do emitowania instrukcji callvirt Intermediate Language (IL) w celu wywołania metod innych niż wirtualne (
callvirt
wykonuje sprawdzanie wartości null, podczas gdy normalne wywołanie nie jest), to zachowanie nie jest niezmienne z kilku powodów:Język C# nie jest jedynym językiem przeznaczonym dla platformy .NET.
Kompilator języka C# coraz częściej próbuje zoptymalizować
callvirt
wywołanie normalne za każdym razem, gdy metoda docelowa nie jest wirtualna i prawdopodobnie nie ma wartości null (np. metody dostępnej za pośrednictwem operatora propagacji wartości null ).
Utworzenie metody wirtualnej oznacza, że kod odbiorcy często wywołuje go niewirtualnie.
❌DISALLOWED: Tworzenie wirtualnego elementu członkowskiego abstrakcyjnego
Wirtualny element członkowski udostępnia implementację metody, która może zostać zastąpiona przez klasę pochodną. Abstrakcyjny element członkowski nie zapewnia implementacji i musi zostać zastąpiony.
❌NIEDOZWOLONE: Dodawanie zapieczętowanego słowa kluczowego do elementu członkowskiego interfejsu
Dodanie
sealed
do domyślnego elementu członkowskiego interfejsu spowoduje, że nie będzie to wirtualne, uniemożliwiając implementację tego elementu członkowskiego pochodnego.❌NIEDOZWOLONE: dodawanie abstrakcyjnego elementu członkowskiego do typu publicznego, który ma dostępne (publiczne lub chronione) konstruktory i które nie są zapieczętowane
❌NIEDOZWOLONE: Dodawanie lub usuwanie statycznego słowa kluczowego z elementu członkowskiego
❌NIEDOZWOLONE: Dodawanie przeciążenia, które wyklucza istniejące przeciążenie i definiuje inne zachowanie
Spowoduje to przerwanie istniejących klientów powiązanych z poprzednim przeciążeniem. Jeśli na przykład klasa ma jedną wersję metody, która akceptuje UInt32element , istniejący odbiorca zostanie pomyślnie powiązany z tym przeciążeniem podczas przekazywania Int32 wartości. Jeśli jednak dodasz przeciążenie, które akceptuje Int32element podczas ponownego kompilowania lub używania opóźnionego powiązania, kompilator wiąże się teraz z nowym przeciążeniem. Jeśli wyniki różnych zachowań, jest to zmiana powodująca niezgodność.
❌DISALLOWED: dodanie konstruktora do klasy, która wcześniej nie miała konstruktora bez dodawania konstruktora bez parametrów
❌✔ NIEDOZWOLONE: dodawanie readonly do pola
❌NIEDOZWOLONE: Zmniejszenie widoczności elementu członkowskiego
Obejmuje to zmniejszenie widoczności chronionego elementu członkowskiego, gdy są dostępne konstruktory (
public
lubprotected
) i typ nie jest zapieczętowany. Jeśli tak nie jest, ograniczenie widoczności chronionego elementu członkowskiego jest dozwolone.Zwiększenie widoczności elementu członkowskiego jest dozwolone.
❌NIEDOZWOLONE: zmiana typu elementu członkowskiego
Nie można zmodyfikować wartości zwracanej metody lub typu właściwości lub pola. Na przykład sygnatura metody zwracającej Object obiekt nie może zostać zmieniona w celu zwrócenia Stringwartości lub odwrotnie.
❌DISALLOWED: Dodawanie pola wystąpienia do struktury, która nie ma pól niepublikowanych
Jeśli struktura ma tylko pola publiczne lub w ogóle nie ma pól, wywołujące mogą deklarować lokalizacje lokalne tego typu struktury bez wywoływania konstruktora struktury lub pierwszego inicjowania lokalnego na
default(T)
, tak długo, jak wszystkie pola publiczne są ustawione na strukturę przed pierwszym użyciem. Dodanie nowych pól — publicznych lub niepublikowanych — do takiej struktury jest zmianą powodującą niezgodność źródła dla tych obiektów wywołujących, ponieważ kompilator będzie teraz wymagać zainicjowania dodatkowych pól.Ponadto dodanie nowych pól — publicznych lub niepublikacyjnych — do struktury bez pól lub tylko pól publicznych jest binarną zmianą powodującą niezgodność w obiektach wywołujących, które zostały zastosowane
[SkipLocalsInit]
do ich kodu. Ponieważ kompilator nie wiedział o tych polach w czasie kompilacji, może emitować il, który nie w pełni inicjuje struktury, co prowadzi do utworzenia struktury na podstawie niezainicjowanych danych stosu.Jeśli struktura ma jakiekolwiek pola niepubliczne, kompilator wymusza już inicjowanie za pomocą konstruktora lub
default(T)
, a dodanie nowych pól wystąpienia nie jest zmianą powodującą niezgodność.❌NIEDOZWOLONE: Wypalanie istniejącego zdarzenia, gdy nigdy wcześniej nie zostało wyzwolone
Zmiany zachowań
Zestawy
✔️ DOZWOLONE: tworzenie zestawu przenośnego, gdy te same platformy są nadal obsługiwane
❌NIEDOZWOLONE: zmiana nazwy zestawu
❌NIEDOZWOLONE: Zmiana klucza publicznego zestawu
Właściwości, pola, parametry i wartości zwracane
✔️ DOZWOLONE: zmiana wartości właściwości, pola, wartości zwracanej lub parametru na bardziej pochodny typ
Na przykład metoda zwracająca typ Object może zwrócić String wystąpienie. (Jednak sygnatura metody nie może ulec zmianie).
✔️ DOZWOLONE: Zwiększenie zakresu akceptowanych wartości dla właściwości lub parametru, jeśli element członkowski nie jest wirtualny
Chociaż zakres wartości, które można przekazać do metody lub są zwracane przez element członkowski, nie można rozwinąć parametru lub typu składowego. Na przykład, gdy wartości przekazane do metody mogą rozwinąć się z zakresu od 0 do 124 do 0–255, typ parametru nie może zmienić się z Byte na Int32.
❌DISALLOWED: Zwiększenie zakresu akceptowanych wartości dla właściwości lub parametru, jeśli element członkowski jest wirtualny
Ta zmiana powoduje przerwanie istniejących przesłonięć elementów członkowskich, które nie będą działać poprawnie dla rozszerzonego zakresu wartości.
❌NIEDOZWOLONE: Zmniejszanie zakresu akceptowanych wartości dla właściwości lub parametru
❌NIEDOZWOLONE: Zwiększanie zakresu zwracanych wartości dla właściwości, pola, wartości zwracanej lub parametru out
❌DISALLOWED: zmiana zwracanych wartości dla właściwości, pola, wartości zwracanej metody lub parametru out
❌NIEDOZWOLONE: zmiana domyślnej wartości właściwości, pola lub parametru
Zmiana lub usunięcie wartości domyślnej parametru nie jest podziałem binarnym. Usunięcie wartości domyślnej parametru jest podziałem źródłowym, a zmiana wartości domyślnej parametru może spowodować przerwanie zachowania po ponownym skompilowaniu.
Z tego powodu usunięcie wartości domyślnych parametrów jest dopuszczalne w konkretnym przypadku "przenoszenia" tych wartości domyślnych do nowego przeciążenia metody w celu wyeliminowania niejednoznaczności. Rozważmy na przykład istniejącą metodę
MyMethod(int a = 1)
. Jeśli wprowadzisz przeciążenie z dwomaMyMethod
opcjonalnymi parametramia
ib
, można zachować zgodność, przenosząc wartośća
domyślną do nowego przeciążenia. Teraz dwa przeciążenia toMyMethod(int a)
iMyMethod(int a = 1, int b = 2)
. Ten wzorzec umożliwiaMyMethod()
skompilowanie.❌NIEDOZWOLONE: Zmiana dokładności wartości zwracanej liczbowej
❓ WYMAGA WYROKU: Zmiana analizy danych wejściowych i zgłaszania nowych wyjątków (nawet jeśli zachowanie analizy nie jest określone w dokumentacji
Wyjątki
✔️ DOZWOLONE: zgłaszanie wyjątku pochodnego niż istniejący wyjątek
Ponieważ nowy wyjątek jest podklasą istniejącego wyjątku, poprzedni kod obsługi wyjątków nadal obsługuje wyjątek. Na przykład w programie .NET Framework 4 metody tworzenia kultury i pobierania zaczęły zgłaszać wartość CultureNotFoundException zamiast ArgumentException , jeśli nie można odnaleźć kultury. Ponieważ CultureNotFoundException pochodzi z ArgumentExceptionmetody , jest to akceptowalna zmiana.
✔️ DOZWOLONE: zgłaszanie bardziej szczegółowego wyjątku niż NotSupportedException, , NotImplementedExceptionNullReferenceException
✔️ DOZWOLONE: zgłaszanie wyjątku, który jest uznawany za nieodwracalny
Nie należy przechwytywać nieodwracalnych wyjątków, ale zamiast tego powinny być obsługiwane przez procedurę obsługi typu catch-all wysokiego poziomu. W związku z tym użytkownicy nie powinni mieć kodu, który przechwytuje te jawne wyjątki. Nieodwracalne wyjątki to:
✔️ DOZWOLONE: zgłaszanie nowego wyjątku w nowej ścieżce kodu
Wyjątek musi dotyczyć tylko nowej ścieżki kodu, która jest wykonywana z nowymi wartościami parametrów lub stanami i których nie można wykonać za pomocą istniejącego kodu, który jest przeznaczony dla poprzedniej wersji.
✔️ DOZWOLONE: Usuwanie wyjątku w celu włączenia bardziej niezawodnego zachowania lub nowych scenariuszy
Na przykład metoda,
Divide
która wcześniej obsługiwała tylko wartości dodatnie i rzuciła ArgumentOutOfRangeException inaczej, można zmienić tak, aby obsługiwała zarówno wartości ujemne, jak i dodatnie bez zgłaszania wyjątku.✔️ DOZWOLONE: zmiana tekstu komunikatu o błędzie
Deweloperzy nie powinni polegać na tekście komunikatów o błędach, które również zmieniają się w oparciu o kulturę użytkownika.
❌NIEDOZWOLONE: Zgłaszanie wyjątku w każdym innym przypadku, który nie został wymieniony powyżej
❌NIEDOZWOLONE: Usuwanie wyjątku w każdym innym przypadku, który nie został wymieniony powyżej
Atrybuty
✔️ DOZWOLONE: zmiana wartości atrybutu, który nie jest zauważalny
❌NIEDOZWOLONE: Zmiana wartości atrybutu, który można zaobserwować
❓ WYMAGA OSĄDU: Usuwanie atrybutu
W większości przypadków usunięcie atrybutu (takiego jak NonSerializedAttribute) jest zmianą powodującą niezgodność.
Obsługa platform
✔️ DOZWOLONE: obsługa operacji na platformie, która wcześniej nie była obsługiwana
❌NIEDOZWOLONE: Brak obsługi lub teraz wymaganie określonego dodatku Service Pack dla operacji, która była wcześniej obsługiwana na platformie
Zmiany implementacji wewnętrznej
❓ WYMAGA OSĄDU: Zmiana obszaru powierzchni typu wewnętrznego
Takie zmiany są ogólnie dozwolone, chociaż przerywają prywatne odbicie. W niektórych przypadkach, gdy popularne biblioteki innych firm lub duża liczba deweloperów zależą od wewnętrznych interfejsów API, takie zmiany mogą nie być dozwolone.
❓ WYMAGA WYROKU: Zmiana wewnętrznej implementacji elementu członkowskiego
Te zmiany są ogólnie dozwolone, chociaż przerywają prywatne odbicie. W niektórych przypadkach, gdy kod klienta często zależy od odbicia prywatnego lub gdy zmiana wprowadza niezamierzone skutki uboczne, te zmiany mogą nie być dozwolone.
✔️ DOZWOLONE: Poprawianie wydajności operacji
Możliwość modyfikowania wydajności operacji jest niezbędna, ale takie zmiany mogą przerwać kod, który opiera się na bieżącej szybkości operacji. Dotyczy to szczególnie kodu, który zależy od chronometrażu operacji asynchronicznych. Zmiana wydajności nie powinna mieć wpływu na inne zachowanie danego interfejsu API; w przeciwnym razie zmiana będzie przerywana.
✔️ DOZWOLONE: Pośrednio (i często niekorzystnie) zmiana wydajności operacji
Jeśli zmiana, o których mowa, nie jest kategoryzowana jako niezgodna z jakiegoś innego powodu, jest to dopuszczalne. Często należy podjąć działania, które mogą obejmować dodatkowe operacje lub dodać nowe funkcje. Będzie to prawie zawsze miało wpływ na wydajność, ale może być niezbędne, aby interfejs API, o której mowa, działał zgodnie z oczekiwaniami.
❌DISALLOWED: zmiana synchronicznego interfejsu API na asynchroniczną (i odwrotnie)
Zmiany kodu
✔️ DOZWOLONE: dodawanie parametrów do parametru
❌DISALLOWED: Zmiana struktury na klasę i odwrotnie
❌DISALLOWED: Dodawanie zaznaczonego słowa kluczowego do bloku kodu
Ta zmiana może spowodować, że kod, który został wcześniej wykonany w celu wyrzucenia elementu OverflowException i jest niedopuszczalny.
❌DISALLOWED: Usuwanie parametrów z parametru
❌NIEDOZWOLONE: zmiana kolejności wyzwalania zdarzeń
Deweloperzy mogą rozsądnie oczekiwać, że zdarzenia będą uruchamiane w tej samej kolejności, a kod dewelopera często zależy od kolejności uruchamiania zdarzeń.
❌NIEDOZWOLONE: Usuwanie wywoływanego zdarzenia w danej akcji
❌NIEDOZWOLONE: zmiana liczby wywołań podanych zdarzeń
❌DISALLOWED: dodawanie elementu FlagsAttribute do typu wyliczenia