58 votes

Raisons impérieuses d'utiliser des interfaces de marquage plutôt que des attributs

Il a été discuté précédemment sur Stack Overflow que nous devrions préférer les attributs aux interfaces de marquage (interfaces sans aucun membre). Article sur la conception d'interfaces sur MSDN affirme également cette recommandation :

Évitez d'utiliser des interfaces de marquage (interfaces sans membres).

Les attributs personnalisés permettent de marquer un type. Pour plus d'informations sur les attributs personnalisés, voir Écrire des attributs personnalisés. Les attributs personnalisés sont préférables lorsque vous pouvez différer la vérification de l'attribut jusqu'à l'exécution du code. Si votre scénario exige une vérification au moment de la compilation, vous ne pouvez pas vous conformer à cette ligne directrice.

Il y a même un Règle FxCop pour mettre en œuvre cette recommandation :

Éviter les interfaces vides

Les interfaces définissent des membres qui fournissent un comportement ou un contrat d'utilisation. La fonctionnalité décrite par l'interface peut être adoptée par n'importe quel type, quelle que soit sa position dans la hiérarchie de l'héritage. Un type implémente une interface en fournissant des implémentations pour les membres de l'interface. Une interface vide ne définit aucun membre et, en tant que telle, ne définit pas de contrat pouvant être mis en œuvre.

Si votre conception inclut des interfaces vides que les types sont censés implémenter, vous utilisez probablement une interface comme marqueur, ou comme moyen d'identifier un groupe de types. Si cette identification doit avoir lieu au moment de l'exécution, la bonne façon d'y parvenir est d'utiliser un attribut personnalisé. Utilisez la présence ou l'absence de l'attribut, ou les propriétés de l'attribut, pour identifier les types cibles. Si l'identification doit avoir lieu à la compilation, l'utilisation d'une interface vide est acceptable.

L'article ne mentionne qu'une seule raison pour laquelle vous pouvez ignorer l'avertissement : lorsque vous avez besoin d'une identification des types au moment de la compilation. (Ceci est cohérent avec l'article sur la conception des interfaces).

Il est possible d'exclure un avertissement de cette règle si l'interface est utilisée pour identifier un ensemble de types au moment de la compilation.

Voici la question proprement dite : Microsoft n'a pas respecté ses propres recommandations dans la conception de la Framework Class Library (au moins dans quelques cas) : Interface IRequiresSessionState y Interface IReadOnlySessionState . Ces interfaces sont utilisées par le cadre ASP.NET pour vérifier s'il doit activer l'état de session pour un gestionnaire spécifique ou non. De toute évidence, elles ne sont pas utilisées pour l'identification des types au moment de la compilation. Pourquoi n'ont-ils pas fait cela ? Je vois deux raisons possibles :

  1. Micro-optimisation : Vérification de l'implémentation d'une interface par un objet ( obj is IReadOnlySessionState ) est plus rapide que l'utilisation de la réflexion pour vérifier la présence d'un attribut ( type.IsDefined(typeof(SessionStateAttribute), true) ). La différence est négligeable la plupart du temps, mais elle peut avoir de l'importance pour un chemin de code critique en termes de performances dans le moteur d'exécution ASP.NET. Cependant, il existe des solutions de contournement, comme la mise en cache du résultat pour chaque type de gestionnaire. Ce qui est intéressant, c'est que les services Web ASMX (qui sont soumis à des caractéristiques de performance similaires) utilisent en fait la fonction EnableSession propriété de la WebMethod attribut à cette fin.

  2. La mise en œuvre d'interfaces est potentiellement plus susceptible d'être prise en charge que la décoration de types avec des attributs par des langages .NET tiers. Puisque ASP.NET est conçu pour être agnostique en matière de langage, et que ASP.NET génère du code pour les types (éventuellement dans un langage tiers à l'aide de la fonction CodeDom ) qui mettent en œuvre lesdites interfaces sur la base de la EnableSessionState de l'attribut <%@ Page %> directive il serait peut-être plus judicieux d'utiliser des interfaces plutôt que des attributs.

Quelles sont les raisons convaincantes d'utiliser des interfaces de marqueurs plutôt que des attributs ?

S'agit-il simplement d'une optimisation (prématurée ?) ou d'une petite erreur dans la conception du cadre ? (Pensent-ils que le reflet est un "grand monstre aux yeux rouges" ?) Réflexions ?

3 votes

Au risque de paraître trop sarcastique, qu'a fait Microsoft pour que vous vous attendiez à ce que l'on vous donne des conseils sur la façon d'utiliser l'Internet ? cohérence de leur part ? Ils ont fourni beaucoup d'outils et de logiciels utiles au fil des ans, mais la seule chose que je n'ai jamais vue de leur part, c'est un comportement cohérent ou le respect de leurs propres lignes directrices ou règles.

5 votes

@jalf : Je suis d'accord qu'ils sont connus pour ne pas adhérer à leurs propres directives, mais pour être juste, ils ont fait du bon travail dans .NET. Le .NET Framework fait preuve d'une grande cohérence et est généralement très bien conçu.

1 votes

en général très bien conçu, oui, et exceptionnellement cohérent, mais pas parfaitement. Il y a beaucoup de petites erreurs de conception stupides qui sont évidentes rétrospectivement. Mais comme l'indique la réponse de Mark, ces problèmes n'étaient probablement pas aussi évidents lors de la conception de la BCL - et les recommandations des lignes directrices ne l'étaient donc pas non plus.

16voto

LBushkin Points 60611

J'évite généralement les "interfaces de marquage" parce qu'elles ne permettent pas de démarquer un type dérivé. Mais cela mis à part, voici quelques cas spécifiques que j'ai vus où les interfaces de marquage seraient préférables à un support de méta-données intégré :

  1. Situations sensibles du point de vue des performances d'exécution.
  2. Compatibilité avec les langages qui ne supportent pas les annotations ou les attributs.
  3. Tout contexte dans lequel le code intéressé n'a pas accès aux métadonnées.
  4. Prise en charge des contraintes génériques et de la variance générique (typiquement des collections).

2 votes

Je dirais que le fait que les interfaces de marquage ne puissent pas être "dé-marquées" dans les types dérivés est souvent une raison convaincante à les utiliser. Si un objet passé à une méthode doit avoir une caractéristique définie par une interface de marqueur, alors theMethod<T>(T it) where T:IHasAttribute le renforcera au moment de la compilation. En revanche, même si l'on sait que Fred possède un attribut, theMethod(Fred it) pourrait se voir passer un objet qui n'a pas cet attribut, ce qui entraînerait un échec à l'exécution. Ceci étant dit, je pense que la plupart des interfaces de type marqueur devraient hériter d'au moins une autre interface.

12voto

Jordão Points 29221

Pour un type générique, on peut vouloir utiliser le même paramètre générique dans une interface de marquage. Cela n'est pas possible avec un attribut :

interface MyInterface<T> {}

class MyClass<T, U> : MyInterface<U> {}

class OtherClass<T, U> : MyInterface<IDictionary<U, T>> {}

Ce type d'interface peut être utile pour associer un type à un autre.

Une autre bonne utilisation d'une interface de marquage est la création d'un type de mélangeur :

interface MyMixin {}

static class MyMixinMethods {
  public static void Method(this MyMixin self) {}
}

class MyClass : MyMixin {
}

En modèle de visiteur acyclique les utilise également. Le terme "interface dégénérée" est également parfois utilisé.

UPDATE :

Je ne sais pas si cela compte, mais je les ai utilisés pour noter les cours d'une école. post-compilateur à travailler.

2 votes

Les chances de pouvoir implémenter efficacement un Mixin sur TOUT type qui pourrait implémenter MyMixin sans aucun membre dans l'interface sont extrêmement faibles.

0 votes

Et je ne comprends pas pourquoi la "permanence" d'une relation a quelque chose à voir avec le fait qu'une interface de marquage soit appropriée ou non. Où utiliseriez-vous l'interface IFood qu'un attribut ne serait pas plus approprié ?

0 votes

@David Nelson : Je suis d'accord, ce n'est pas vraiment différent de l'utilisation d'un attribut. Mais l'utilisation du mixin est intéressante, je n'ai pas compris votre commentaire à ce sujet.

7voto

Mark Seemann Points 102767

Microsoft n'a pas suivi strictement les lignes directrices lorsqu'il a créé .NET 1.0, parce que les lignes directrices ont évolué en même temps que le cadre, et que certaines règles n'ont été apprises que lorsqu'il était trop tard pour modifier l'API.

IIRC, les exemples que vous mentionnez appartiennent à BCL 1.0, ce qui expliquerait cela.

Ceci est expliqué dans Lignes directrices pour la conception du cadre .


Cela dit, le livre fait également remarquer que "[l]es tests d'attributs sont beaucoup plus coûteux que la vérification de type" (dans un encadré de Rico Mariani).

Il ajoute que l'on a parfois besoin de l'interface du marqueur pour effectuer des vérifications au moment de la compilation, ce qui n'est pas possible avec un attribut. Cependant, l'exemple donné dans le livre (p. 88) n'est pas convaincant et je ne le répéterai donc pas ici.

0 votes

Certes, on les voit souvent enfreindre leurs propres lignes directrices, mais il y a généralement des raisons à cela. La question est de savoir pourquoi ils n'ont pas suivi cette ligne directrice spécifique. Y a-t-il des avantages réels ou s'agit-il simplement d'une erreur de conception ?

2 votes

J'ai toujours pensé qu'il s'agissait d'une erreur. Je n'ai pas le livre sur moi en ce moment, mais je crois qu'il parle spécifiquement d'exemples où ils ont utilisé des interfaces de marqueurs parce qu'ils ne savaient pas faire mieux...

0 votes

Maintenant que j'ai eu l'occasion de consulter le livre, j'ai ajouté quelques informations supplémentaires à ma réponse.

4voto

herzmeister Points 7077

Je suis très favorable à Marker Interfaces. Je n'ai jamais aimé Attributs. Je les vois comme une sorte de méta-information pour les classes et les membres, destinée par exemple aux débogueurs. Comme pour les exceptions, ils ne devraient pas influencer la logique de traitement normale, à mon humble avis.

1 votes

Pouvez-vous expliquer pourquoi vous pensez ainsi dans votre réponse ? Je veux dire pourquoi pensez-vous que les attributs et les exceptions ne devraient pas "influencer la logique normale de traitement" ? (Qu'entendez-vous par "logique de traitement normale" ? Les exceptions influencent certainement le flux du programme et ne sont pas là uniquement pour les débogueurs).

1 votes

D'accord, je vais essayer de développer un peu. Tout d'abord, les attributs .NET (ou les annotations Java) sont une invention relativement récente, je pense qu'il n'existe même pas de représentation standard en UML pour eux. Je sais qu'ils sont maintenant largement utilisés pour l'AOP et le post-traitement (PostSharp et al), mais cela ne fait qu'essayer de compenser un certain manque de fonctionnalités ou une trop grande restrictivité du langage de programmation.

1 votes

Par "logique de traitement normale", j'entends le "flux de contrôle" régulier d'une application ou d'un système, qui n'est pas "exceptionnel". Certaines personnes lancent des exceptions sauvages lorsqu'elles veulent renvoyer quelque chose qui ne correspond pas au type de données simple qu'elles ont choisi, alors qu'elles auraient mieux fait de modéliser les choses différemment. Il en va de même avec les attributs. Prenons par exemple les attributs [MaxLength(x)] pour les métadonnées des propriétés, ou l'attribut [DefaultValue("foo")] qui est mon ami spécial parce qu'il n'est pas localisable.

3voto

mikeng Points 906

Du point de vue du codage, je pense que je préfère la syntaxe de l'interface du marqueur en raison des mots-clés intégrés. as y is . Le marquage des attributs nécessite un peu plus de code.

[MarkedByAttribute]
public class MarkedClass : IMarkByInterface
{
}

public class MarkedByAttributeAttribute : Attribute
{
}

public interface IMarkByInterface
{
}

public static class AttributeExtension
{
    public static bool HasAttibute<T>(this object obj)
    {
        var hasAttribute = Attribute.GetCustomAttribute(obj.GetType(), typeof(T));
        return hasAttribute != null;
    }
}

Et quelques tests pour utiliser le code :

using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class ClassMarkingTests
{
    private MarkedClass _markedClass;

    [TestInitialize]
    public void Init()
    {
        _markedClass = new MarkedClass();
    }

    [TestMethod]
    public void TestClassAttributeMarking()
    {
        var hasMarkerAttribute = _markedClass.HasAttibute<MarkedByAttributeAttribute>();
        Assert.IsTrue(hasMarkerAttribute);
    }

    [TestMethod]
    public void TestClassInterfaceMarking()
    {
        var hasMarkerInterface = _markedClass as IMarkByInterface;
        Assert.IsTrue(hasMarkerInterface != null);            
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X