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 '
J'ai commencé à 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.Enum
dans une contrainte générique. J'ai obtenu les mêmes résultats avec System.Object
, System.Delegate
, System.MulticastDelegate
et System.ValueType
aussi.
Y en a-t-il plus? Je ne trouve aucune information sur les "classes spéciales" en C #.
Aussi, qu'est-ce est si spécial au sujet de ces classes que nous ne pouvons pas les utiliser comme une contrainte de type générique?
D'après le code source de Roslyn, il 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"
J'ai trouvé un commentaire de Jon Skeet de 2008 sur une question similaire: pourquoi le System.Enum
contrainte non prise en charge.
Je sais que c'est un peu hors sujet , mais il a demandé à 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 général des artefacts de la langue, pas tellement le 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 du travail de langage.)
Deuxièmement, j'aimerais personnellement avoir des contraintes de délégué, 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, etc.)
Cependant, en raison de restrictions de planification, nous ne serons probablement pas en mesure d'intégrer ces fonctionnalités dans la prochaine version de la langue.
Selon MSDN c'est une liste statique de classes:
Erreur du compilateur CS0702
La contrainte ne peut pas être une classe spéciale 'identificateur' Les types suivants ne peuvent pas être utilisés comme contraintes:
Selon la spécification du langage C # 4.0 (codé: [10.1.5] Contraintes des paramètres de type), dit deux choses:
1] Le type ne doit pas être objet. Étant donné que tous les types dérivent d'un objet, une telle contrainte n'aurait aucun effet si elle était autorisée.
2] Si T n'a pas de contraintes primaires ou de contraintes de paramètres de type, sa classe de base effective est objet.
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 essaie d'instancier votre classe en utilisant 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 interdira au type générique d'être un type de 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:
Certaines classes du Framework 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'interdit pas d'utiliser ces classes comme contraintes, mais les types génériques qui leur sont soumis n'acquerraient pas les caractéristiques non héritées comme le feraient les types concrets. Les créateurs de C # ont décidé que, parce qu'un tel comportement pouvait dérouter certaines personnes et qu'ils n'y voyaient aucune utilité, ils devraient interdire ces 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 dest
et source
aux méthodes qui attendent un argument de type System.Array
; en outre, on obtiendrait une validation au moment de la compilation que dest
et source
étaient les types de tableau compatibles, mais on ne pourrait pas accéder aux éléments du tableau à l'aide de l'opérateur []
.
L'incapacité à utiliser Array
comme contrainte est généralement assez facile à contourner, puisque 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: l'ancienne méthode fonctionnerait dans le scénario où l'un ou les deux arguments étaient de type System.Array
Tout en rejetant les cas où les arguments sont des types de tableau incompatibles; l'ajout d'une surcharge où les deux arguments étaient de type System.Array
ferait accepter au code les cas supplémentaires qu'il devrait accepter, mais le ferait également accepter par erreur les cas qu'il ne devrait pas.
Je trouve la décision d'interdire la plupart des contraintes spéciales gênante. Le seul qui n'aurait aucune signification sémantique serait System.Object
[Car si c'était légal comme contrainte, tout le satisferait]. System.ValueType
Ne serait probablement pas très utile, car les références de type ValueType
n'ont pas vraiment grand chose en commun avec les types de valeur, mais elles pourraient vraisemblablement avoir une certaine valeur dans les cas impliquant Reflection. System.Enum
Et System.Delegate
Auraient tous deux de réelles utilisations, mais comme les créateurs de C # n'y ont pas pensé, ils sont interdits sans raison valable.
Les éléments suivants peuvent être trouvés dans CLR via C # 4th Edition:
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.
Je ne pense pas qu'il existe une définition officielle des "classes spéciales"/"types spéciaux".
Vous pouvez penser à eux a types, qui ne peuvent pas être utilisés avec des sémantiques de types "réguliers":
P.S. J'ajouterais System.Void
à la liste.