Qu'est-ce qu'une «classe spéciale» exactement?

114

Après avoir échoué à obtenir quelque chose comme ce qui suit à compiler:

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

avec l'erreur

Une contrainte ne peut pas être une classe spéciale `System.Array '

Je commençais à me demander, qu'est - ce exactement est une « classe spéciale »?

Les gens semblent souvent obtenir le même type d'erreur lorsqu'ils spécifient System.Enumdans une contrainte générique. Je suis les mêmes résultats avec System.Object, System.Delegate, System.MulticastDelegateet System.ValueTypeaussi.

Y en a-t-il plus? Je ne trouve aucune information sur les "classes spéciales" en C #.

De plus, qu'est - ce qui est si spécial dans ces classes que nous ne pouvons pas les utiliser comme contrainte de type générique?

Mints97
la source
14
Je ne pense pas que ce soit une copie directe. La question n'est pas "pourquoi je ne peux pas utiliser cela comme une contrainte", c'est "quelles sont ces classes spéciales". J'ai jeté un coup d'œil à ces questions et elles expliquent simplement pourquoi il serait inutile de l'utiliser comme contrainte, sans expliquer ce qu'est réellement une "classe spéciale" et pourquoi elle est considérée comme spéciale.
Adam Houldsworth
2
D'après mon expérience, les classes qui sont utilisées mais que vous ne pouvez pas les utiliser directement, uniquement implicitement via une autre syntaxe, sont des classes spéciales. Enum appartient à la même catégorie. Ce qui les rend vraiment spéciaux, je ne sais pas.
Lasse V. Karlsen
@AndyKorneyev: cette question est en quelque sorte différente. Je demande une définition d'une «classe spéciale» et / ou une liste complète de celles-ci. Cette question demande simplement la raison pour laquelle System.Array ne peut pas être une contrainte de type générique.
Mints97
D'après la documentation, il déclare "[...] seuls le système et les compilateurs peuvent dériver explicitement de la classe Array.". C'est probablement ce qui en fait une classe spéciale - elle est traitée spécialement par le compilateur.
RB.
1
@RB .: faux. Cette logique signifierait que ce System.Objectn'est pas une "classe spéciale", comme cela est valide:, public class X : System.Object { }mais System.Objectest toujours une "classe spéciale".
Mints97

Réponses:

106

D'après le code source de Roslyn, cela ressemble à une liste de types codés en dur:

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

Source: Binder_Constraints.cs IsValidConstraintType
Je l'ai trouvé en utilisant une recherche GitHub: "Une contrainte ne peut pas être une classe spéciale"

Kobi
la source
1
@kobi 702 devient l'erreur du compilateur CS0702, comme on le voit dans la sortie du compilateur (que cette question a omis de citer) et d'autres réponses.
AakashM
1
@AakashM - Merci! J'ai essayé de compiler et je n'ai pas obtenu le numéro d'erreur, pour une raison quelconque. Il m'a alors fallu près de 5 minutes pour le découvrir et je n'ai pas eu assez de temps pour modifier mon commentaire. Histoire triste.
Kobi
1
@Kobi: vous devez regarder la fenêtre de sortie , vous y trouvez le numéro de code d'erreur du compilateur exact CS0702.
Tim Schmelter
9
Alors maintenant, la vraie question est de savoir pourquoi ces classes spéciales?
David dit réintégrer Monica le
@DavidGrinberg Peut-être que la raison est que vous ne pouvez pas hériter de ces types directement (sauf pour object), ou du moins cela a quelque chose à voir avec cela. En outre where T : Arraypermettrait le passage de dosage comme T, ce qui est probablement pas ce que la plupart des gens veulent.
IllidanS4 veut que Monica revienne le
42

J'ai trouvé un commentaire de Jon Skeet de 2008 sur une question similaire: pourquoi la System.Enumcontrainte n'est-elle pas prise en charge?

Je sais que c'est un peu hors sujet , mais il a interrogé Eric Lippert (l'équipe C #) à ce sujet et ils ont fourni cette réponse:

Tout d'abord, votre conjecture est correcte; les restrictions sur les contraintes sont en gros des artefacts de la langue, pas tellement du CLR. (Si nous faisions ces fonctionnalités, il y aurait quelques petites choses que nous aimerions changer dans le CLR concernant la façon dont les types énumérables sont spécifiés, mais ce serait surtout un travail de langage.)

Deuxièmement, j'aimerais personnellement avoir des contraintes de délégation, des contraintes d'énumération et la possibilité de spécifier des contraintes qui sont illégales aujourd'hui parce que le compilateur essaie de vous sauver de vous-même. (Autrement dit, rendre les types scellés légaux en tant que contraintes, et ainsi de suite.)

Cependant, en raison de restrictions de planification, nous ne serons probablement pas en mesure d'intégrer ces fonctionnalités dans la prochaine version du langage.

Amir Popovich
la source
10
@YuvalItzchakov - Est-il préférable de citer Github \ MSDN? L'équipe C # a donné une réponse concrète concernant le problème ou un problème similaire. Cela ne peut vraiment blesser personne. Jon Skeet vient de les citer et est assez fiable quand il arrive à C # ..
Amir Popovich
5
Pas besoin de s'énerver. Je ne voulais pas dire que ce n'est pas une réponse valable :) Je partageais juste mes réflexions sur la fondation qui est jonskeet; p
Yuval Itzchakov
40
FYI BTW, je pense que c'est moi que vous citez ici. :-)
Eric Lippert
2
@EricLippert - Cela rend le devis encore plus fiable.
Amir Popovich
Le domaine du lien en réponse est mort.
Pang du
25

Selon MSDN, c'est une liste statique de classes:

Erreur du compilateur CS0702

La contrainte ne peut pas être un 'identifiant' de classe spéciale Les types suivants ne peuvent pas être utilisés comme contraintes:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.
Tim Schmelter
la source
4
Cool, semble être la bonne réponse, bonne trouvaille! Mais où est System.MulticastDelegatela liste?
Mints97
8
@ Mints97: aucune idée, manque de documentation peut-être?
Tim Schmelter
On dirait que vous ne pouvez pas non plus hériter de ces classes.
David Klempfner
14

Selon la spécification du langage C # 4.0 (codé: [10.1.5] Contraintes de paramètre de type), deux choses:

1] Le type ne doit pas être objet. Étant donné que tous les types dérivent d'objet, une telle contrainte n'aurait aucun effet si elle était autorisée.

2] Si T n'a pas de contraintes primaires ou de paramètres de type, sa classe de base effective est object.

Lorsque vous définissez une classe générique, vous pouvez appliquer des restrictions aux types de types que le code client peut utiliser pour les arguments de type lorsqu'il instancie votre classe. Si le code client tente d'instancier votre classe à l'aide d'un type qui n'est pas autorisé par une contrainte, le résultat est une erreur de compilation. Ces restrictions sont appelées contraintes. Les contraintes sont spécifiées à l'aide du mot clé contextuel where. Si vous souhaitez contraindre un type générique à être un type de référence, utilisez: class.

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

Cela empêchera le type générique d'être un type valeur, tel que int ou une structure, etc.

De plus, la contrainte ne peut pas être un 'identificateur' de classe spéciale Les types suivants ne peuvent pas être utilisés comme contraintes:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.
Rahul Nikate
la source
12

Il existe certaines classes dans le Framework qui transmettent effectivement des caractéristiques spéciales à tous les types qui en dérivent mais ne possèdent pas ces caractéristiques elles-mêmes . Le CLR lui-même n'impose aucune interdiction d'utiliser ces classes comme contraintes, mais les types génériques qui y sont contraints n'acquériraient pas les caractéristiques non héritées comme le feraient des types concrets. Les créateurs de C # ont décidé que, parce qu'un tel comportement pouvait semer la confusion chez certaines personnes et qu'ils ne voyaient aucune utilité, ils devraient interdire de telles contraintes plutôt que de leur permettre de se comporter comme ils le font dans le CLR.

Si, par exemple, on était autorisé à écrire void CopyArray<T>(T dest, T source, int start, int count):; on pourrait passer destet sourceà des méthodes qui attendent un argument de type System.Array; de plus, on obtiendrait la validation à la compilation qui destet sourceétaient les types de tableaux compatibles, mais on ne pourrait pas accéder aux éléments du tableau en utilisant l' []opérateur.

L'incapacité à utiliser Arraycomme contrainte est généralement assez facile à contourner, car void CopyArray<T>(T[] dest, T[] source, int start, int count)fonctionnera dans presque toutes les situations où l'ancienne méthode fonctionnerait. Elle a cependant une faiblesse: la première méthode fonctionnerait dans le scénario où l'un des arguments ou les deux étaient de type System.Arraytout en rejetant les cas où les arguments sont des types de tableaux incompatibles; l'ajout d'une surcharge où les deux arguments étaient de type System.Arrayferait en sorte que le code accepte les cas supplémentaires qu'il devrait accepter, mais aussi le ferait accepter par erreur les cas qu'il ne devrait pas.

Je trouve la décision d'interdire la plupart des contraintes spéciales ennuyeuse. Le seul qui aurait une signification sémantique nulle serait System.Object[puisque si c'était légal comme contrainte, tout le satisferait]. System.ValueTypene serait probablement pas très utile, car les références de type ValueTypen'ont pas vraiment grand chose en commun avec les types valeur, mais cela pourrait vraisemblablement avoir une certaine valeur dans les cas impliquant Reflection. Les deux System.Enumet System.Delegateaurait des utilisations réelles, mais étant donné que les créateurs de C # ne pensaient pas d'eux , ils sont mis hors la loi sans raison.

supercat
la source
10

Les éléments suivants peuvent être trouvés dans CLR via C # 4th Edition:

Contraintes primaires

Un paramètre de type peut spécifier zéro contrainte principale ou une contrainte principale. Une contrainte principale peut être un type de référence qui identifie une classe qui n'est pas scellée. Vous ne pouvez pas spécifier l'un des types de référence spéciaux suivants: System.Object , System.Array , System.Delegate , System.MulticastDelegate , System.ValueType , System.Enum ou System.Void . Lorsque vous spécifiez une contrainte de type de référence, vous promettez au compilateur qu'un argument de type spécifié sera soit du même type, soit d'un type dérivé du type de contrainte.

Claudio P
la source
Voir aussi: C # LS section 10.1.4.1: La classe de base directe d'un type de classe ne doit pas être l' un des types suivants: System.Array, System.Delegate, System.MulticastDelegate, System.Enumou System.ValueType. De plus, une déclaration de classe générique ne peut pas être utilisée System.Attributecomme classe de base directe ou indirecte.
Jeroen Vannevel
5

Je ne pense pas qu'il existe une définition officielle des "classes spéciales" / "types spéciaux".

Vous pouvez penser à eux des types aa, qui ne peuvent pas être utilisés avec la sémantique des types "réguliers":

  • vous ne pouvez pas les instancier directement;
  • vous ne pouvez pas en hériter directement de type personnalisé;
  • il y a de la magie du compilateur pour travailler avec eux (facultativement);
  • l'utilisation directe de leurs instances au moins inutile (éventuellement; imaginez que vous avez créé générique ci-dessus, quel code générique allez-vous écrire?)

PS j'ajouterais System.Voidà la liste.

Dennis
la source
2
System.Voiddonne une erreur entièrement différente lorsqu'il est utilisé comme contrainte générique =)
Mints97
@ Mints97: vrai. Mais si la question porte sur «spécial», alors oui, voidc'est très spécial. :)
Dennis
@Dennis: Le code qui a quelques paramètres d'un type contraint à System.Arraypourrait utiliser des méthodes comme Array.Copydéplacer des données de l'un à l'autre; le code avec des paramètres d'un type contraint à System.Delegatepourrait être utilisé Delegate.Combinesur eux et convertir le résultat dans le type approprié . Utiliser efficacement un type générique connu Enumutilisera Reflection une fois pour chacun de ces types, mais une HasAnyFlagméthode générique peut être 10 fois plus rapide qu'une méthode non générique.
supercat