Che cos'è esattamente una "classe speciale"?


114

Dopo aver fallito nel ottenere qualcosa di simile al seguente da compilare:

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

con l'errore

Un vincolo non può essere la classe speciale `System.Array '

Ho iniziato a chiedermi, che cosa esattamente è una "classe speciale"?

Le persone spesso sembrano ricevere lo stesso tipo di errore quando specificano System.Enumin un vincolo generico. Ho ottenuto gli stessi risultati con System.Object, System.Delegate, System.MulticastDelegatee System.ValueTypetroppo.

Ce ne sono di più? Non riesco a trovare alcuna informazione su "classi speciali" in C #.

Inoltre, cosa c'è di così speciale in quelle classi da non poterle usare come vincolo di tipo generico?


14
Non penso che questo sia un duplicato diretto. La domanda non è "perché non posso usarlo come vincolo", è "cosa sono queste classi speciali". Ho dato un'occhiata a queste domande e mi hanno semplicemente spiegato perché sarebbe inutile usarle come vincolo, senza spiegare cosa sia effettivamente una "classe speciale" e perché sia ​​considerata speciale.
Adam Houldsworth

2
Nella mia esperienza, le classi che vengono utilizzate ma non è possibile utilizzarle direttamente, solo implicitamente attraverso un'altra sintassi, sono classi speciali. Enum rientra nella stessa categoria. Cosa li rende esattamente speciali, non lo so.
Lasse V. Karlsen

@ AndyKorneyev: questa domanda è diversa. Chiedo una definizione di "classe speciale" e / o un elenco completo di queste. Questa domanda chiede semplicemente il motivo per cui System.Array non può essere un vincolo di tipo generico.
Zecche 97

Dalla documentazione si afferma "[...] solo il sistema e i compilatori possono derivare esplicitamente dalla classe Array.". È probabile che questo sia ciò che la rende una classe speciale: viene trattata in modo speciale dal compilatore.
RB.

1
@RB .: sbagliato. Questa logica significherebbe nonSystem.Object è una "classe speciale", poiché è valida:, ma è pur sempre una "classe speciale". public class X : System.Object { }System.Object
Zecche 97

Risposte:


106

Dal codice sorgente di Roslyn, sembra un elenco di tipi hardcoded:

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;
}

Fonte: Binder_Constraints.cs IsValidConstraintType
L'ho trovato utilizzando una ricerca GitHub: "Un vincolo non può essere una classe speciale"


1
@kobi 702 diventa l'errore del compilatore CS0702, come si vede nell'output del compilatore (che questa domanda ha trascurato di citare) e altre risposte.
AakashM

1
@AakashM - Grazie! Ho provato a compilare e non ho ricevuto il numero di errore, per qualche motivo. Mi ci sono voluti quasi 5 minuti per scoprirlo e non ho avuto abbastanza tempo per modificare il mio commento. Triste vicenda.
Kobi

1
@ Kobi: devi guardare la finestra di output , lì trovi il numero esatto del codice di errore del compilatore CS0702.
Tim Schmelter

9
Quindi ora la vera domanda è perché queste classi speciali?
David dice Reinstate Monica

@DavidGrinberg Forse il motivo è che non puoi ereditare direttamente da questi tipi (ad eccezione di object), o almeno ha qualcosa a che fare con questo. Inoltre where T : Arrayconsentirebbe di passare il test come T, che probabilmente non è ciò che la maggior parte delle persone desidera.
IllidanS4 vuole che Monica torni il

42

Ho trovato un commento di Jon Skeet del 2008 su una domanda simile: perché il System.Enumvincolo non è supportato.

So che questo è un po 'fuori tema , ma ha chiesto a Eric Lippert (il team di C #) e hanno fornito questa risposta:

Prima di tutto, la tua congettura è corretta; le restrizioni sui vincoli sono in gran parte artefatti del linguaggio, non tanto il CLR. (Se dovessimo eseguire queste funzionalità, ci sarebbero alcune piccole cose che vorremmo cambiare nel CLR riguardo a come vengono specificati i tipi enumerabili, ma principalmente questo sarebbe un lavoro linguistico.)

In secondo luogo, personalmente mi piacerebbe avere vincoli delegati, vincoli enum e la possibilità di specificare vincoli che oggi sono illegali perché il compilatore sta cercando di salvarti da te stesso. (Cioè, rendere i tipi sigillati legali come vincoli e così via.)

Tuttavia, a causa delle limitazioni di pianificazione, probabilmente non saremo in grado di inserire queste funzionalità nella prossima versione della lingua.


10
@YuvalItzchakov - È meglio citare Github \ MSDN? Il team di C # ha dato una risposta concreta in merito al problema o simile .. Non può davvero ferire nessuno. Jon Skeet li ha appena citati ed è abbastanza affidabile quando si arriva a C # ..
Amir Popovich

5
Non c'è bisogno di arrabbiarsi. Non intendevo che questa non fosse una risposta valida :) Stavo solo condividendo i miei pensieri sulla fondazione che è jonskeet; p
Yuval Itzchakov

40
Cordiali saluti, penso di essere io che stai citando lì. :-)
Eric Lippert

2
@EricLippert - Ciò rende la citazione ancora più affidabile.
Amir Popovich

Il dominio del collegamento nella risposta è morto.
Pang

25

Secondo MSDN è un elenco statico di classi:

Errore del compilatore CS0702

Il vincolo non può essere un 'identificatore' di classe speciale I seguenti tipi non possono essere utilizzati come vincoli:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.

4
Fantastico, sembra la risposta giusta, buona scoperta! Ma dov'è System.MulticastDelegatenella lista?
Zecche 97

8
@ Mints97: nessuna idea, forse mancanza di documentazione?
Tim Schmelter

Sembra che anche tu non possa ereditare da queste classi.
David Klempfner

14

Secondo C # 4.0 Language Specification (Coded: [10.1.5] Type parameter constraints) dice due cose:

1] Il tipo non deve essere oggetto. Poiché tutti i tipi derivano da object, tale vincolo non avrebbe alcun effetto se fosse consentito.

2] Se T non ha vincoli primari o vincoli di parametro di tipo, la sua classe base effettiva è oggetto.

Quando si definisce una classe generica, è possibile applicare restrizioni ai tipi di tipi che il codice client può utilizzare per gli argomenti di tipo quando crea un'istanza della classe. Se il codice client tenta di creare un'istanza della classe utilizzando un tipo non consentito da un vincolo, il risultato è un errore in fase di compilazione. Queste restrizioni sono chiamate vincoli. I vincoli vengono specificati utilizzando la parola chiave contestuale where. Se vuoi vincolare un tipo generico come tipo di riferimento, usa: class.

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

Ciò impedirà al tipo generico di essere un tipo di valore, come int o uno struct ecc.

Inoltre, il vincolo non può essere un 'identificatore' di classe speciale I seguenti tipi non possono essere utilizzati come vincoli:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.

12

Ci sono alcune classi nel Framework che trasmettono effettivamente caratteristiche speciali a tutti i tipi da esse derivati ma non possiedono tali caratteristiche stesse . Lo stesso CLR non impone alcun divieto contro l'utilizzo di tali classi come vincoli, ma i tipi generici ad esse vincolati non acquisiscono le caratteristiche non ereditate come farebbero i tipi concreti. I creatori di C # hanno deciso che poiché tale comportamento potrebbe confondere alcune persone e non sono riusciti a vederne l'utilità, dovrebbero vietare tali vincoli piuttosto che consentire loro di comportarsi come fanno nel CLR.

Se, ad esempio, fosse consentito scrivere void CopyArray<T>(T dest, T source, int start, int count):; si sarebbe in grado di passare deste sourcea metodi che prevedono un argomento di tipo System.Array; inoltre, si otterrebbe una convalida in fase di compilazione che deste sourcefossero i tipi di array compatibili, ma non si sarebbe in grado di accedere agli elementi dell'array utilizzando l' []operatore.

L'incapacità di utilizzare Arraycome vincolo è per lo più abbastanza facile da aggirare, poiché void CopyArray<T>(T[] dest, T[] source, int start, int count)funzionerà in quasi tutte le situazioni in cui funzionerebbe il primo metodo. Tuttavia, ha un punto debole: il primo metodo funzionerebbe nello scenario in cui uno o entrambi gli argomenti erano di tipo System.Arrayrifiutando i casi in cui gli argomenti sono tipi di array incompatibili; l'aggiunta di un sovraccarico in cui entrambi gli argomenti erano di tipo System.Arrayfarebbe sì che il codice accetti i casi aggiuntivi che dovrebbe accettare, ma lo farebbe anche accettare erroneamente casi che non dovrebbe.

Trovo fastidiosa la decisione di mettere fuori legge la maggior parte dei vincoli speciali. L'unico che avrebbe significato semantico zero sarebbe System.Object[poiché se fosse legale come vincolo, qualsiasi cosa lo soddisferebbe]. System.ValueTypeprobabilmente non sarebbe molto utile, poiché i riferimenti di tipo ValueTypenon hanno molto in comune con i tipi di valore, ma potrebbe plausibilmente avere qualche valore nei casi che coinvolgono la riflessione. Entrambi System.Enume System.Delegateavrebbero alcuni usi reali, ma dal momento che i creatori di C # non ci hanno pensato, sono fuorilegge senza una buona ragione.


10

È possibile trovare quanto segue in CLR tramite C # 4th Edition:

Vincoli primari

Un parametro di tipo può specificare zero vincoli primari o un vincolo primario. Un vincolo primario può essere un tipo di riferimento che identifica una classe che non è sigillata. Non è possibile specificare uno dei seguenti tipi di riferimento speciale: System.Object , System.Array , System.Delegate , System.MulticastDelegate , System.ValueType , System.Enum o System.Void . Quando si specifica un vincolo di tipo di riferimento, si promette al compilatore che un argomento di tipo specificato sarà dello stesso tipo o di un tipo derivato dal tipo di vincolo.


Vedi anche: C # LS sezione 10.1.4.1: La classe di base diretta di un tipo di classe non deve essere uno dei seguenti tipi: System.Array, System.Delegate, System.MulticastDelegate, System.Enum, o System.ValueType. Inoltre, una dichiarazione di classe generica non può essere utilizzata System.Attributecome classe base diretta o indiretta.
Jeroen Vannevel

5

Non credo che esista una definizione ufficiale di "classi speciali" / "tipi speciali".

Potresti pensare a loro come tipi aa, che non possono essere usati con la semantica dei tipi "regolari":

  • non puoi istanziarli direttamente;
  • non puoi ereditare direttamente il tipo personalizzato da loro;
  • c'è qualche magia del compilatore per lavorare con loro (opzionalmente);
  • l'uso diretto delle loro istanze almeno inutile (opzionalmente; immagina, che hai creato generico sopra, quale codice generico stai per scrivere?)

PS Aggiungerei System.Voidalla lista.


2
System.Voiddà un errore completamente diverso se usato come vincolo generico =)
Mints97

@ Mints97: vero. Ma se la domanda riguarda "speciale", allora sì, voidè molto speciale. :)
Dennis

@Dennis: il codice che ha un paio di parametri di un tipo vincolato System.Arraypotrebbe utilizzare metodi come Array.Copyspostare i dati da uno all'altro; il codice con parametri di un tipo vincolato System.Delegatepotrebbe essere utilizzato Delegate.Combinesu di essi e eseguire il cast del risultato nel tipo corretto . Fare un uso efficace di un tipo noto generico Enumutilizzerà Reflection una volta per ciascuno di questi tipi, ma un HasAnyFlagmetodo generico può essere 10 volte più veloce di un metodo non generico.
supercat
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.