7
votes

Les syndicats discriminés de type-sécurité en C # ou: Comment limiter le nombre d'implémentations d'une interface?

Premier, désolé pour le long post. Fondamentalement, ma question est la suivante:

J'essaie de reproduire le type de syndicat discriminé F # discriminé suivant en C #: p> xxx pré>

peut suggérer une solution d'interface plus simple que ce qui suit? p>


xxx pré>

Tous mes algorithmes reconnaissent ces trois types de relation, et celles-ci seulement, donc je dois empêcher toute nouvelle implémentation de Irelation code> par troisième parties (autres assemblées). P>

Note de bas de page: strong> à certains, cela pourrait survenir que si je viens de recevoir mon interface et mon algorithme droit en termes d'orientation / polymorphisme, cela ne devrait pas avoir d'importance qu'un tiers -Les implémentations de PARTY sont injectées dans mes méthodes d'algorithme, tant que l'interface est correctement mise en œuvre. Ceci est une critique valide. Mais supposons simplement que pour le moment que je favorise un style de programmation plus fonctionnel sur une orientation d'objet stricte dans ce cas. SUB> EM> P> blockQuote>

Ma meilleure idée jusqu'à présent est de déclarer tous les types ci-dessus comme interne code> (c.-à-d., ils ne seront jamais vus directement par des outsiders) et créer un type de proxy relation Code>, qui sera le seul type visible à des tiers: p> xxx pré>

tout va bien jusqu'à présent, mais il devient plus élaboré ... p>

  • ... Si j'expose les méthodes d'usine pour les types de relation concrets: p>

    public … ExposedAlgorithm(this IEnumerable<Relation> relations)
    {
        // forward unwrapped IRelation objects to an internal algorithm method:
        return InternalAlgorithm(from relation in relations select relation.value);
    }
    
  • ... Chaque fois que j'expose un algorithme travaillant sur des types de relation, car je dois mapper de / vers le type de proxy: p>

    public Relation CreateLessThanRelation(…)
    {
        return new Relation { value = new LessThanRelation { … } };
    }
    


6 commentaires

Vous savez que si vous utilisez une structure à travers son interface, la structure est en boîte, non? Stackoverflow.com/Questions/63671/...


@Delnan , parlez-vous de "limiter le nombre de mises en œuvre d'une interface" (puis voyez la "note de bas de page" au milieu de ma réponse) ou "faire des syndicats discriminés dans C #"? @xanatos , bon point! Je ne pensais pas à cela, car je ne suis pas trop préoccupé par la performance en ce moment ... ou suggérez-vous une simple modification de struct à classe , même si Cela n'a pas de sens (comme ces types doivent avoir une sémantique de valeur)?


@Xanatos - Il y a une exception à cela (n'est pas toujours là?), mais ce n'est pas ça ...


xanatos un type générique T avec une contrainte d'interface générique T: isomeinterface; Ceci est alors contraint plutôt que boxé; ce qui signifie que l'appel approprié est effectué sans besoin de casser. Il y a un code OP spécial juste pour cela.


(Il s'applique également à une méthode non générique sur un type générique, évidemment)


Bonjour, je mae une bibliothèque de Draign Du's en C #. Vous avez une classe de base qui a une CTOR interne et vous pouvez sceller les sous-types. Github.com/mcintyre321/oneof


3 Réponses :


13
votes

Limiter l'interface Interface signifie qu'il n'agit pas vraiment comme une interface (qui devrait accepter toute mise en œuvre (substitution), telle que décorateurs) - donc je ne peux donc pas recommander cela.

En outre, notez qu'avec une petite exception des génériques, le traitement d'une structure comme une interface conduit à la boxe.

de sorte que cela laisse un cas intéressant; Un résumé la classe avec un constructeur privé et un nombre connu d'implémentations comme types imbriqués , ce qui signifie qu'ils ont accès au constructeur privé.

Maintenant, vous contrôlez les sous-types, la boxe n'est pas un problème (comme c'est une classe), et il y a moins d'espoir de substitution.


3 commentaires

J'aurais aimé mes types de mise en œuvre pour être struct s (c'est comme ça que j'ai fini par des interfaces en premier lieu, car l'héritage ne fonctionne pas avec des types de valeur), mais vous faites un bon cas contre ce.


et en utilisant un constructeur interne sans classes imbriquées (mais dans la même assemblée)?


@Xanatos - Oui, non imbriqué et interne est à peu près le même



3
votes

J'irais avec l'idée basée sur Enum . En fait, j'utiliserais cette solution en F # aussi. Puisque vous n'avez toujours que deux arguments, vous n'avez pas vraiment besoin d'union discriminée: xxx

en général, si vous souhaitez encoder une union discriminée en C #, il est probablement la meilleure option à utiliser < EM> Classe de base abstraite puis hérité de classe pour chaque cas (avec des champs supplémentaires). Étant donné que vous ne planifiez pas de l'étendre en ajoutant de nouvelles sous-classes, vous pouvez définir tag Enum qui répertorie tous les sous-types possibles (afin que vous puissiez facilement implémenter "correspondance de modèle" par commutateur sur la balise).


1 commentaires

Concernant votre suggestion F #: Je suppose que la différence n'est que dans la manière dont on correspond aux modèles, par exemple: correspondant avec | MOASTHAN (_, _) -> ... vs. correspondance Relation avec | (La moins, _, _, _) -> ... . Existe-t-il des prestations de performance ou de mémoire à cette dernière approche?



6
votes

Je pense que votre approche générale va dans la bonne direction, mais on dirait que vous pouvez simplifier votre code à l'aide de classes abstraits:

public abstract class Relation
{
    internal Relation(object subject, object obj)
    {
        Subject = subject;
        Object = obj;
    }
    public object Subject { get; private set; }
    public object Object { get; private set; }
}

public sealed class LessThanRelation : Relation
{
    public LessThanRelation(object subject, object obj) : base(subject, obj) { }
}

public sealed class EqualToRelation : Relation
{
    public EqualToRelation(object subject, object obj) : base(subject, obj) { }
}

public sealed class GreaterThanRelation : Relation
{
    public GreaterThanRelation(object subject, object obj) : base(subject, obj) { }
}


3 commentaires

Merci, Jon Skeet, pour le poteau de blog qui a inspiré cette réponse :) msmvps.com/blogs/jon_skeet/archive/2010/10/03/...


Ou, comme le suggère Xanatos dans un commentaire à une autre réponse, vous pouvez simplement créer le constructeur de classe de base interne et déposer la méthode getnull méthode ...?


+1 @stakx: Je ne sais pas pourquoi je ne pensais pas à cela :) réponse mise à jour en conséquence.