Pourquoi C # interdit-il les types d'attributs génériques?

510

Cela provoque une exception au moment de la compilation:

public sealed class ValidatesAttribute<T> : Attribute
{

}

[Validates<string>]
public static class StringValidation
{

}

Je me rends compte que C # ne prend pas en charge les attributs génériques. Cependant, après beaucoup de recherches sur Google, je n'arrive pas à trouver la raison.

Est-ce que quelqu'un sait pourquoi les types génériques ne peuvent pas dériver Attribute? Des théories?

Bryan Watts
la source
17
Vous pouvez faire [Valide (typeof (chaîne)] - Je suis d'accord que les génériques seraient plus agréables ...
ConsultUtah
20
Même si c'est un ajout très tardif à cette question, il est triste que non seulement les attributs eux-mêmes mais aussi les classes d'attributs abstraits (qui ne peuvent évidemment pas être instanciés de toute façon) ne soient pas autorisés, comme ceci: abstract class Base<T>: Attribute {}qui pourraient être utilisés pour créer des non classes dérivées génériques comme ceci:class Concrete: Base<MyType> {}
Lucero
88
J'ai soif d'attributs génériques et d'attributs acceptant des lambdas. Imaginez des choses comme [DependsOnProperty<Foo>(f => f.Bar)]ou [ForeignKey<Foo>(f => f.IdBar)]...
Jacek Gorgoń
3
Ce serait extrêmement utile dans une situation que je viens de rencontrer; il serait intéressant de créer un LinkedValueAttribute qui accepte un type générique et applique ce type sur la valeur réelle spécifiée. Je pourrais l'utiliser pour les énumérations pour spécifier la valeur "par défaut" d'une autre énumération qui devrait être utilisée si cette valeur énumérative est choisie. Plusieurs de ces attributs pourraient être spécifiés pour différents types, et je pourrais obtenir la valeur dont j'ai besoin en fonction du type dont j'ai besoin. Je peux le configurer pour utiliser Type et Object mais être fortement tapé serait un énorme avantage.
KeithS
10
Si cela ne vous dérange pas un peu d'IL, cela semble prometteur .
Jarrod Dixon

Réponses:

358

Eh bien, je ne peux pas dire pourquoi il n'est pas disponible, mais je peux confirmer que ce n'est pas un problème CLI. La spécification CLI ne le mentionne pas (pour autant que je puisse voir) et si vous utilisez directement IL, vous pouvez créer un attribut générique. La partie de la spécification C # 3 qui l'interdit - la section 10.1.4 «Spécification de base de classe» ne donne aucune justification.

La spécification ECMA C # 2 annotée ne donne pas non plus d'informations utiles, bien qu'elle fournisse un exemple de ce qui n'est pas autorisé.

Ma copie de la spécification annotée C # 3 devrait arriver demain ... Je vais voir si cela donne plus d'informations. Quoi qu'il en soit, c'est définitivement une décision de langue plutôt qu'une décision d'exécution.

EDIT: Réponse d'Eric Lippert (paraphrasé): pas de raison particulière, sauf pour éviter la complexité du langage et du compilateur pour un cas d'utilisation qui n'ajoute pas beaucoup de valeur.

Jon Skeet
la source
139
"sauf pour éviter la complexité à la fois du langage et du compilateur" ... et cela des gens qui nous donnent co et contravariance ...
flq
254
"cas d'utilisation qui n'ajoute pas beaucoup de valeur"? C'est une opinion subjective, cela pourrait me donner beaucoup de valeur!
Jon Kruger,
34
Ce qui me dérange le plus de ne pas avoir cette fonctionnalité, c'est de ne pas pouvoir faire des choses comme [PropertyReference (x => x.SomeProperty)]. Au lieu de cela, vous avez besoin de chaînes magiques et de typeof (), ce qui, je pense, est un peu nul.
Asbjørn Ulsberg
13
@John: Je pense que vous sous-estimez largement le coût de conception, de spécification, d'implémentation et de test d'une nouvelle fonctionnalité de langue.
Jon Skeet
14
Je veux juste ajouter à la défense de @ Timwi que ce n'est pas tout à fait le seul endroit où ils sont discutés , et les 13 000 points de vue sur cette question impliquent un bon niveau d'intérêt. Aussi: merci d'avoir obtenu une réponse faisant autorité, Jon.
Jordan Grey
84

Un attribut décore une classe au moment de la compilation, mais une classe générique ne reçoit ses informations de type final qu'au moment de l'exécution. Étant donné que l'attribut peut affecter la compilation, il doit être "complet" au moment de la compilation.

Consultez cet article MSDN pour plus d'informations.

GalacticCowboy
la source
3
L'article réaffirme qu'ils ne sont pas possibles, mais sans raison. Je comprends conceptuellement votre réponse. Connaissez-vous une documentation plus officielle sur la question?
Bryan Watts
2
L'article couvre le fait que l'IL contient toujours des espaces réservés génériques qui sont remplacés par le type réel au moment de l'exécution. Le reste a été déduit par moi ... :)
GalacticCowboy
1
Pour ce que ça vaut, VB applique la même contrainte: "Les classes qui sont génériques ou contenues dans un type générique ne peuvent pas hériter d'une classe d'attributs."
GalacticCowboy
1
ECMA-334, section 14.16 dit "Les expressions constantes sont requises dans les contextes répertoriés ci-dessous et cela est indiqué dans la grammaire en utilisant constante-expression. Dans ces contextes, une erreur de compilation se produit si une expression ne peut pas être entièrement évaluée lors de la compilation- temps." Les attributs sont dans la liste.
GalacticCowboy
4
Cela semble être contredit par une autre réponse qui déclare que l'IL le permettra. ( stackoverflow.com/a/294259/3195477 )
UuDdLrLrSs
22

Je ne sais pas pourquoi ce n'est pas autorisé, mais c'est une solution de contournement possible

[AttributeUsage(AttributeTargets.Class)]
public class ClassDescriptionAttribute : Attribute
{
    public ClassDescriptionAttribute(Type KeyDataType)
    {
        _KeyDataType = KeyDataType;
    }

    public Type KeyDataType
    {
        get { return _KeyDataType; }
    }
    private Type _KeyDataType;
}


[ClassDescriptionAttribute(typeof(string))]
class Program
{
    ....
}
GeekyMonkey
la source
3
Malheureusement, vous perdez la saisie au moment de la compilation lors de la consommation de l'attribut. Imaginez que l'attribut crée quelque chose de type générique. Vous pouvez contourner cela, mais ce serait bien; c'est une de ces choses intuitives que vous ne pouvez pas faire, comme la variance (actuellement).
Bryan Watts
14
Malheureusement, essayer de ne pas le faire est la raison pour laquelle j'ai trouvé cette question SO. Je suppose que je vais juste devoir m'en tenir au typeof. Cela semble vraiment un mot-clé sale maintenant après que les génériques existent depuis si longtemps maintenant.
Chris Marisic
13

Ce n'est pas vraiment générique et vous devez toujours écrire une classe d'attribut spécifique par type, mais vous pouvez peut-être utiliser une interface de base générique pour coder un peu de manière défensive, écrire moins de code que nécessaire, tirer parti du polymorphisme, etc.

//an interface which means it can't have its own implementation. 
//You might need to use extension methods on this interface for that.
public interface ValidatesAttribute<T>
{
    T Value { get; } //or whatever that is
    bool IsValid { get; } //etc
}

public class ValidatesStringAttribute : Attribute, ValidatesAttribute<string>
{
    //...
}
public class ValidatesIntAttribute : Attribute, ValidatesAttribute<int>
{
    //...
}

[ValidatesString]
public static class StringValidation
{

}
[ValidatesInt]
public static class IntValidation
{

}
nawfal
la source
8

C'est une très bonne question. Dans mon expérience avec des attributs, je pense que la contrainte est en place parce que lorsque l'on réfléchit sur un attribut créerait une condition dans laquelle vous devez vérifier toutes les permutations possibles de type: typeof(Validates<string>), typeof(Validates<SomeCustomType>), etc ...

À mon avis, si une validation personnalisée est requise selon le type, un attribut peut ne pas être la meilleure approche.

Peut-être qu'une classe de validation qui prend un SomeCustomValidationDelegateou un ISomeCustomValidatorcomme paramètre serait une meilleure approche.

ichiban
la source
Je suis d'accord avec toi. Je me pose cette question depuis longtemps et je suis en train de construire un système de validation. J'ai utilisé ma terminologie actuelle pour poser la question, mais je n'ai pas l'intention de mettre en œuvre une approche basée sur ce mécanisme.
Bryan Watts
Je suis tombé dessus tout en travaillant sur un design dans le même but: la validation. J'essaie de le faire d'une manière qui est facile à analyser à la fois automatiquement (c'est-à-dire que vous pourriez générer un rapport décrivant la validation dans la demande de confirmation) et par un humain visualisant le code. Si ce n'est pas des attributs, je ne sais pas quelle serait la meilleure solution ... Je pourrais toujours essayer la conception des attributs, mais déclarer manuellement des attributs spécifiques au type. C'est un peu plus de travail, mais l'objectif est la fiabilité de connaître les règles de validation (et de pouvoir en rendre compte pour confirmation).
bambams
4
vous pouvez vérifier les définitions de type génériques (c.-à-d. typeof (valide <>)) ...
Melvyn
5

Ce n'est pas actuellement une fonctionnalité en langage C #, mais il y a beaucoup de discussions sur le dépôt officiel en langage C # .

De quelques notes de réunion :

Même si cela fonctionnerait en principe, il existe des bogues dans la plupart des versions du runtime afin qu'il ne fonctionne pas correctement (il n'a jamais été exercé).

Nous avons besoin d'un mécanisme pour comprendre sur quel runtime cible il fonctionne. Nous en avons besoin pour bien des choses, et nous examinons actuellement la question. Jusque-là, nous ne pouvons pas le supporter.

Candidat pour une version C # majeure, si nous pouvons faire en sorte qu'un nombre suffisant de versions d'exécution le traitent.

Owen Pauling
la source
1

Ma solution de contournement est quelque chose comme ceci:

public class DistinctType1IdValidation : ValidationAttribute
{
    private readonly DistinctValidator<Type1> validator;

    public DistinctIdValidation()
    {
        validator = new DistinctValidator<Type1>(x=>x.Id);
    }

    public override bool IsValid(object value)
    {
        return validator.IsValid(value);
    }
}

public class DistinctType2NameValidation : ValidationAttribute
{
    private readonly DistinctValidator<Type2> validator;

    public DistinctType2NameValidation()
    {
        validator = new DistinctValidator<Type2>(x=>x.Name);
    }

    public override bool IsValid(object value)
    {
        return validator.IsValid(value);
    }
}

...
[DataMember, DistinctType1IdValidation ]
public Type1[] Items { get; set; }

[DataMember, DistinctType2NameValidation ]
public Type2[] Items { get; set; }
razon
la source