Czym dokładnie jest „klasa specjalna”?

114

Po nieudanej kompilacji czegoś podobnego do następującego:

public class Gen<T> where T : System.Array
{
}

z błędem

Ograniczenie nie może być klasą specjalną `` System.Array ''

Zacząłem się zastanawiać, czym właściwie „specjalne zajęcia”?

Ludziom często wydaje się, że popełniają ten sam rodzaj błędu, gdy określają System.Enumw ogólnym ograniczeniu. Mam te same rezultaty System.Object, System.Delegate, System.MulticastDelegatei System.ValueTypeteż.

Czy jest ich więcej? Nie mogę znaleźć żadnych informacji o „klasach specjalnych” w C #.

Co jest takiego specjalnego w tych klasach, że nie możemy ich używać jako ogólnego ograniczenia typu?

Mennice97
źródło
14
Nie sądzę, żeby to był bezpośredni duplikat. Pytanie nie brzmi „dlaczego nie mogę tego użyć jako ograniczenia”, lecz „czym są te specjalne klasy”. Rzuciłem okiem na te pytania, a one po prostu wyjaśniają, dlaczego używanie go jako ograniczenia byłoby bezużyteczne, nie wyjaśniając, czym właściwie jest „klasa specjalna” i dlaczego jest uważana za wyjątkową.
Adam Houldsworth
2
Z mojego doświadczenia wynika, że ​​klasy, które są używane, ale nie można ich używać bezpośrednio, tylko pośrednio za pomocą innej składni, są klasami specjalnymi. Enum należy do tej samej kategorii. Co dokładnie czyni je wyjątkowymi, nie wiem.
Lasse V. Karlsen
@AndyKorneyev: to pytanie jest trochę inne. Proszę o zdefiniowanie „klasy specjalnej” i / lub ich wyczerpującą listę. To pytanie dotyczy po prostu powodu, dla którego System.Array nie może być ogólnym ograniczeniem typu.
Mints97
Z dokumentacji wynika, że ​​„[...] tylko system i kompilatory mogą bezpośrednio wywodzić z klasy Array.”. Prawdopodobnie właśnie to sprawia, że ​​jest to klasa specjalna - kompilator traktuje ją specjalnie.
RB.
1
@RB .: źle. Ta logika oznaczałaby, że nieSystem.Object jest to „klasa specjalna”, ponieważ jest to poprawne: ale nadal jest „klasą specjalną”. public class X : System.Object { }System.Object
Mints97

Odpowiedzi:

106

Z kodu źródłowego Roslyn wygląda to na listę typów zakodowanych na stałe:

switch (type.SpecialType)
{
    case SpecialType.System_Object:
    case SpecialType.System_ValueType:
    case SpecialType.System_Enum:
    case SpecialType.System_Delegate:
    case SpecialType.System_MulticastDelegate:
    case SpecialType.System_Array:
        // "Constraint cannot be special class '{0}'"
        Error(diagnostics, ErrorCode.ERR_SpecialTypeAsBound, syntax, type);
        return false;
}

Źródło: Binder_Constraints.cs IsValidConstraintType
Znalazłem to za pomocą wyszukiwania GitHub: „Ograniczenie nie może być klasą specjalną”

Kobi
źródło
1
@kobi 702 staje się błędem kompilatora CS0702, co widać w danych wyjściowych kompilatora (które to pytanie pominięto w cytowaniu) i innych odpowiedziach.
AakashM
1
@AakashM - Dzięki! Próbowałem skompilować i z jakiegoś powodu nie otrzymałem numeru błędu. Dowiedziałem się wtedy prawie 5 minut i nie miałem wystarczająco dużo czasu, aby edytować mój komentarz. Smutna historia.
Kobi
1
@Kobi: musisz spojrzeć na wyjście -window, tam znajdziesz dokładny numer kodu błędu kompilatora CS0702.
Tim Schmelter
9
Więc teraz prawdziwe pytanie brzmi: dlaczego są to specjalne zajęcia?
David mówi „Przywróć Monikę”
@DavidGrinberg Może powodem jest to, że nie możesz dziedziczyć po tych typach bezpośrednio (z wyjątkiem object), a przynajmniej ma to z tym coś wspólnego. where T : ArrayPozwoliłoby również na zaliczenie testu jako T, co prawdopodobnie nie jest tym, czego chce większość ludzi.
IllidanS4 chce, aby Monica wróciła
42

Znalazłem komentarz Jona Skeeta z 2008 r. Dotyczący podobnego pytania: Dlaczego to System.Enumograniczenie nie jest obsługiwane.

Wiem, że to trochę nie na temat , ale zapytał o to Erica Lipperta (zespół C #), a oni udzielili takiej odpowiedzi:

Po pierwsze, twoje przypuszczenie jest słuszne; ograniczenia dotyczące ograniczeń są w dużej mierze artefaktami języka, a nie CLR. (Gdybyśmy mieli wykonać te funkcje, byłoby kilka drobnych rzeczy, które chcielibyśmy zmienić w CLR, dotyczących sposobu określania wyliczalnych typów, ale głównie byłaby to praca językowa.)

Po drugie, osobiście chciałbym mieć ograniczenia delegowania, ograniczenia wyliczeniowe i możliwość określenia ograniczeń, które są obecnie nielegalne, ponieważ kompilator próbuje cię uratować przed sobą. (Oznacza to, że zapieczętowane typy są legalne jako ograniczenia i tak dalej).

Jednak ze względu na ograniczenia w harmonogramie prawdopodobnie nie będziemy w stanie wprowadzić tych funkcji do następnej wersji języka.

Amir Popovich
źródło
10
@YuvalItzchakov - Czy cytowanie Github \ MSDN jest lepsze? Zespół C # udzielił konkretnej odpowiedzi dotyczącej problemu lub podobnej… To naprawdę nikomu nie zaszkodzi. Jon Skeet właśnie je zacytował i jest całkiem niezawodny, gdy dotrze do C # ..
Amir Popovich
5
Nie musisz się denerwować. Nie chodziło mi o to, że to nie jest prawidłowa odpowiedź :) Właśnie dzieliłem się przemyśleniami na temat fundacji, którą jest jonskeet; p
Yuval Itzchakov
40
FYI BTW Myślę, że to ja tam cytujesz. :-)
Eric Lippert
2
@EricLippert - dzięki temu wycena jest jeszcze bardziej wiarygodna.
Amir Popovich
Domena linku w odpowiedzi jest martwa.
Pang,
25

Według MSDN jest to statyczna lista klas:

Błąd kompilatora CS0702

Ograniczenie nie może być specjalnym identyfikatorem klasy Następujących typów nie można używać jako ograniczeń:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.
Tim Schmelter
źródło
4
Fajnie, wydaje się, że to dobra odpowiedź, dobre znalezisko! Ale gdzie jest System.MulticastDelegatena liście?
Mints97
8
@ Mints97: nie mam pojęcia, może brak dokumentacji?
Tim Schmelter
Wygląda na to, że nie możesz również dziedziczyć z tych klas.
David Klempfner
14

Zgodnie ze specyfikacją języka C # 4.0 (kodowane: [10.1.5] ograniczenia parametrów typu) mówi się o dwóch rzeczach:

1] Typ nie może być obiektem. Ponieważ wszystkie typy pochodzą od obiektu, takie ograniczenie nie miałoby żadnego efektu, gdyby było dozwolone.

2] Jeśli T nie ma ograniczeń podstawowych ani ograniczeń parametrów typu, jego efektywną klasą bazową jest object.

Podczas definiowania klasy ogólnej można zastosować ograniczenia do rodzajów typów, których kod klienta może używać dla argumentów typu podczas tworzenia wystąpienia klasy. Jeśli kod klienta próbuje utworzyć wystąpienie klasy przy użyciu typu, który nie jest dozwolony przez ograniczenie, wynikiem jest błąd w czasie kompilacji. Te ograniczenia nazywane są ograniczeniami. Ograniczenia są określane przy użyciu kontekstowego słowa kluczowego where. Jeśli chcesz ograniczyć typ ogólny, aby był typem referencyjnym, użyj: class.

public class Gen<T> where T : class
{
}

Dzięki temu typ ogólny nie będzie typem wartości, takim jak int lub struct itp.

Ponadto ograniczenie nie może być specjalnym „identyfikatorem” klasy Następujących typów nie można używać jako ograniczeń:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.
Rahul Nikate
źródło
12

Istnieją pewne klasy w Framework, które skutecznie przekazują specjalne cechy wszystkim typom z nich pochodnym, ale same nie posiadają tych cech . Sam CLR nie nakłada zakazu używania tych klas jako ograniczeń, ale typy ogólne ograniczone do nich nie uzyskałyby niedziedziczonych cech, tak jak typy konkretne. Twórcy C # zdecydowali, że ponieważ takie zachowanie może dezorientować niektórych ludzi, a nie dostrzegli w nim żadnej użyteczności, powinni raczej zakazać takich ograniczeń, niż pozwolić im zachowywać się tak, jak robią w CLR.

Gdyby na przykład wolno było pisać void CopyArray<T>(T dest, T source, int start, int count):; można by przekazać desti sourcedo metod, które oczekują argumentu typu System.Array; ponadto, można by uzyskać walidację w czasie kompilacji, że desti sourcebyły zgodne typy tablic, ale nie można uzyskać dostępu do elementów tablicy za pomocą []operatora.

Niemożność użycia Arrayjako ograniczenia jest przeważnie dość łatwa do obejścia, ponieważ void CopyArray<T>(T[] dest, T[] source, int start, int count)będzie działać w prawie wszystkich sytuacjach, w których zadziała pierwsza metoda. Ma jednak pewną wadę: pierwsza metoda działałaby w scenariuszu, w którym jeden lub oba argumenty były typu, System.Arraypodczas odrzucania przypadków, w których argumenty są niezgodnymi typami tablicowymi; dodanie przeciążenia, w którym oba argumenty byłyby typu System.Array, spowodowałoby, że kod zaakceptowałby dodatkowe przypadki, które powinien zaakceptować, ale także błędnie zaakceptowałoby przypadki, których nie powinien.

Uważam, że decyzja o zdelegalizowaniu większości specjalnych ograniczeń jest irytująca. Jedynym, który miałby zerowe znaczenie semantyczne, byłoby System.Object[ponieważ gdyby było to legalne jako ograniczenie, wszystko by je spełniało]. System.ValueTypeprawdopodobnie nie byłby zbyt przydatny, ponieważ referencje typu ValueTypetak naprawdę nie mają wiele wspólnego z typami wartości, ale prawdopodobnie mogą mieć jakąś wartość w przypadkach obejmujących odbicie. Oba System.Enumi System.Delegatemiałyby pewne rzeczywiste zastosowania, ale ponieważ twórcy C # nie pomyśleli o nich, są wyjęci spod prawa bez dobrego powodu.

superkat
źródło
10

Następujące elementy można znaleźć w środowisku CLR za pośrednictwem C # 4th Edition:

Podstawowe ograniczenia

Parametr typu może określać zero ograniczeń podstawowych lub jedno ograniczenie podstawowe. Głównym ograniczeniem może być typ referencyjny, który identyfikuje klasę, która nie jest zapieczętowana. Nie można określić jednego z następujących specjalnych typów odwołań: System.Object , System.Array , System.Delegate , System.MulticastDelegate , System.ValueType , System.Enum lub System.Void . Określając ograniczenie typu referencyjnego, obiecujesz kompilatorowi, że określony argument typu będzie tego samego typu lub typu pochodzącego z typu ograniczenia.

Claudio P
źródło
Patrz również: sekcja C # ls 10.1.4.1: bezpośrednia klasa bazowa typu klasy nie musi być jeden z następujących typów: System.Array, System.Delegate, System.MulticastDelegate, System.Enum, lub System.ValueType. Ponadto deklaracji klasy ogólnej nie można używać System.Attributejako bezpośredniej ani pośredniej klasy bazowej.
Jeroen Vannevel
5

Nie sądzę, żeby istniała jakakolwiek oficjalna definicja „klas specjalnych” / „typów specjalnych”.

Możesz pomyśleć o nich jako o typach, których nie można używać z semantycznymi typami „zwykłymi”:

  • nie możesz utworzyć ich bezpośrednio;
  • nie możesz bezpośrednio dziedziczyć po nich typu niestandardowego;
  • jest trochę magii kompilatora do pracy z nimi (opcjonalnie);
  • bezpośrednie użycie ich instancji jest co najmniej bezużyteczne (opcjonalnie; wyobraź sobie, że stworzyłeś ogólny kod powyżej, jaki ogólny kod zamierzasz napisać?)

PS dodałbym System.Voiddo listy.

Dennis
źródło
2
System.Voiddaje zupełnie inny błąd, gdy jest używany jako ogólne ograniczenie =)
Mints97
@ Mints97: prawda. Ale jeśli pytanie dotyczy „specjalnego”, to tak, voidjest bardzo szczególne. :)
Dennis
@Dennis: Kod, który ma kilka parametrów określonego typu, System.Arraymoże używać metod takich jak Array.Copyprzenoszenie danych z jednego do drugiego; kod z parametrami typu ograniczonego do System.Delegatebędzie mógł Delegate.Combinena nich użyć i rzutować wynik na właściwy typ . Efektywne wykorzystanie ogólnego znanego typu Enumbędzie używać Reflection raz dla każdego takiego typu, ale HasAnyFlagmetoda ogólna może być 10 razy szybsza niż metoda nieogólna.
supercat