Question

Je suis dans la situation où beaucoup de mes classes sont des conteneurs d'objets bien connus, mais désordonnées de différents types, par exemple un récipient peut se présenter comme suit:

public class Container
{
    public A A { get; private set; }
    public B B { get; private set; }
    public C C { get; private set; }

    public bool StoreIfKnown(object o)
    {
        // TODO...
    }
}

Donc, si o est de type A il doit être stocké dans la propriété A, le type B dans la propriété B et ainsi de suite.

En F # la méthode StoreIfKnown pourrait être écrit quelque chose comme (excusez les erreurs de syntaxe, mon F # est pas grand et assez rouillé):

match o with
| ?: A a -> A <- a; true
| ?: B b -> B <- b; true
| ?: C c -> C <- c; true
| _ -> false

Mais en C # la seule façon semble être le plutôt bavard:

if (o is A)
{
    this.A = (A)o;
    return true;
}

if (o is B)
{
    this.B = (B)o;
    return true;
}

// etc.

return false;

Je pourrais le faire avec le mot-clé as pour éviter le test / modèle moulé qui serait plus rapide, mais il est encore plus bavard.

Y at-il de façon élégante de le faire en C #?

Était-ce utile?

La solution

Vous pouvez l'auteur d'une méthode d'extension sur « o » et des classes d'aide pour permettre à un modèle de programmation comme

o.Match<A>( a => { this.A = a; return true; } )
 .Match<B>( b => { this.B = b; return true; } )
 .Else( () => { return false; } )

Mais méfiez-vous de faire trop comme DSL carriole ici, de peur que vous vous retrouvez avec une API que vous comprenez.

Voir aussi

http://blogs.msdn.com/lucabol/archive/2008/07/15/ac-library-to-write-functional-code-part-v-the-match-operator. aspx

Autres conseils

ne est pas aussi astucieux que la solution de Brian, mais cela ne nécessite pas la définition d'une nouvelle DSL. Vous remarquerez que votre répéter le code suivant:

if (o is {DataType})
{
    {Property} = ({DataType})o;
    return true;
}

Son assez facile de tirer ce modèle dans sa propre méthode, ce qui quelque chose comme ceci:

public class Container
{
    public A A { get; private set; }
    public B B { get; private set; }
    public C C { get; private set; }

    private bool TestProp<T>(object o, Action<T> f)
    {
        if (o is T)
            return false;

        f((T)o);
        return true;
    }

    public bool StoreIfKnown(object o)
    {
        return
            TestProp<A>(o, x => A = x) ||
            TestProp<B>(o, x => B = x) ||
            TestProp<C>(o, x => C = x) ||
            false;
    }
}

Si vous travaillez avec des types de référence, vous pouvez profiter de l'inférence de type avec les réglages suivants:

    private bool TestProp<T>(T o, Action<T> f)
    {
        if (o == null)
            return false;

        f(o);
        return true;
    }

    public bool StoreIfKnown(object o)
    {
        return
            TestProp(o as A, x => A = x) ||
            TestProp(o as B, x => B = x) ||
            TestProp(o as C, x => C = x) ||
            false;
    }

Je joue autour avec un petit constructeur de correspondance (inspiré par réponse de Brian) qui permet la vérification des types, des clauses de garde, et le retour d'un résultat de l'ensemble. Il utilise inférence de type donc le seul endroit où vous devez spécifier un type est l'endroit où vous voulez vraiment.

Alors, imaginer C type a une propriété IsActive que nous voulons être true, il ressemblerait à quelque chose comme ceci:

var stored = Match.Against(o)
    .When<A>().Then(a => { this.A = a; return true; })
    .When<B>().Then(b => { this.B = b; return true; })
    .When<C>(c => c.IsActive).Then(c => { this.C = c; return true; })
    .Otherwise(a => false);

Ce que je pense est assez facile à lire, en particulier car il permet un prédicat à exécuter contre le type dérivé avant correspondant réellement ce qui est quelque chose que j'ai besoin.

Le code est assez long car il a besoin d'un certain nombre de classes de constructeur partiellement spécifiées dans l'arrière-plan pour permettre l'inférence de type de travail, donc je ne peux pas vraiment le poster ici. Mais si quelqu'un est intéressé laissez-moi savoir dans les commentaires et je vais le coller sur mon blog et mettre un lien ici.

Bart de Smet est allé une fois fou avec correspondance de motif, à partir ici (va tout le chemin jusqu'à la partie 8). Si vous parvenez jamais passer à travers tout ce contenu il ne devrait pas y avoir de questions à poser à la correspondance de C #. S'il y a, ils ne peuvent probablement pas être répondues par stackoverflow:)

En Août 2016 et aperçu de C # 7.0, il y a un support limité pour motif correspondant . Vous pouvez essayer si en utilisant Visual Studio « 15 » preview 4 .

Selon le blog MSDN, vous pouvez utiliser des modèles en deux endroits:

  • sur le côté droit de est expressions

  • clauses de cas instructions switch

motifs possibles sont:

  
      
  • modes constants de la forme c (où c est une expression constante en C #), qui teste que l'entrée est égale à c

  •   
  • Type motifs de la forme T x (où T est un type et x est un identificateur), qui teste que l'entrée est de type T, et si oui, extrait la valeur de l'entrée dans une nouvelle variable x de type T

  •   
  • motifs Var de la forme var x (où x est un identificateur), qui correspond toujours, et il suffit de mettre la valeur de l'entrée dans une nouvelle variable x avec le même type que l'entrée

  •   

Je n'ai pas installé Visual Studio 15, donc je ne suis pas sûr que je réécris correctement votre code, mais il ne devrait pas être loin:

public class Container
{
    public A A { get; private set; }
    public B B { get; private set; }
    public C C { get; private set; }

    public bool StoreIfKnown(object obj)
    {
        switch (obj)
        {
            case A a:
                this.A = a
                // I don't put "break" because I'm returning value from a method
                return true;
            case B b:
                this.B = b
                return true;
            case C c:
                this.C = c
                return true;
            default:
                WriteLine("<other>");
                return false;
        }
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top