Commutateur ou un dictionnaire lors de l'attribution à un nouvel objet

12

Récemment, je suis venu à préférer le mappage des relations 1-1 en utilisant Dictionariesau lieu des Switchinstructions. Je trouve que c'est un peu plus rapide à écrire et plus facile à traiter mentalement. Malheureusement, lors du mappage vers une nouvelle instance d'un objet, je ne veux pas le définir comme ceci:

var fooDict = new Dictionary<int, IBigObject>()
{
    { 0, new Foo() }, // Creates an instance of Foo
    { 1, new Bar() }, // Creates an instance of Bar
    { 2, new Baz() }  // Creates an instance of Baz
}

var quux = fooDict[0]; // quux references Foo

Compte tenu de cette construction, j'ai gaspillé des cycles CPU et de la mémoire en créant 3 objets, en faisant tout ce que leurs constructeurs pourraient contenir, et je n'ai fini par utiliser l'un d'eux. Je crois également que le mappage d'autres objets fooDict[0]dans ce cas les amènera à référencer la même chose, plutôt que de créer une nouvelle instance de Foocomme prévu. Une solution serait d'utiliser un lambda à la place:

var fooDict = new Dictionary<int, Func<IBigObject>>()
{
    { 0, () => new Foo() }, // Returns a new instance of Foo when invoked
    { 1, () => new Bar() }, // Ditto Bar
    { 2, () => new Baz() }  // Ditto Baz
}

var quux = fooDict[0](); // equivalent to saying 'var quux = new Foo();'

Est-ce que cela arrive à un point où c'est trop déroutant? C'est facile de manquer ça ()à la fin. Ou le mappage à une fonction / expression est-il une pratique assez courante? L'alternative serait d'utiliser un interrupteur:

IBigObject quux;
switch(someInt)
{
    case 0: quux = new Foo(); break;
    case 1: quux = new Bar(); break;
    case 2: quux = new Baz(); break;
}

Quelle invocation est la plus acceptable?

  • Dictionnaire, pour des recherches plus rapides et moins de mots-clés (casse et pause)
  • Switch: plus communément trouvé dans le code, ne nécessite pas l'utilisation d'un objet Func <> pour l'indirection.
KChaloux
la source
2
sans lambda, la même instance sera renvoyée chaque fois que vous effectuez la recherche avec la même clé (comme dans fooDict[0] is fooDict[0]). avec le lambda et le commutateur ce n'est pas le cas
ratchet freak
@ratchetfreak Oui, j'ai réalisé cela en tapant l'exemple. Je pense que j'en ai pris note quelque part.
KChaloux
1
Je suppose que le fait que vous l'ayez explicitement mis dans une constante signifie que vous avez besoin que l'objet créé soit mutable. Mais si un jour vous pouvez les rendre immuables, alors renvoyer directement l'objet sera la meilleure solution. Vous pouvez mettre le dict dans un champ const et n'encourir le coût de la création qu'une seule fois dans toute l'application.
Laurent Bourgault-Roy

Réponses:

7

C'est une interprétation intéressante du modèle d'usine . J'aime la combinaison du dictionnaire et de l'expression lambda; cela m'a fait regarder ce conteneur d'une nouvelle façon.

J'ignore la préoccupation dans votre question concernant les cycles de processeur, comme vous l'avez mentionné dans les commentaires selon lesquels l'approche non lambda ne fournit pas ce dont vous avez besoin.

Je pense que l'une ou l'autre approche (commutateur vs dictionnaire + lambda) ira bien. La seule limitation est qu'en utilisant le dictionnaire, vous limitez les types d'entrées que vous pourriez recevoir afin de générer la classe retournée.

L'utilisation d'une instruction switch vous offrirait plus de flexibilité sur les paramètres d'entrée. Cependant, si cela devait être un problème, vous pourriez envelopper le dictionnaire à l'intérieur d'une méthode et avoir le même résultat final.

Si c'est nouveau pour votre équipe, commentez le code et expliquez ce qui se passe. Appelez pour une révision du code d'équipe, expliquez-leur ce qui a été fait et informez-le. A part ça, ça a l'air bien.


la source
Malheureusement, il y a environ un mois, mon équipe se compose uniquement de moi (le responsable a quitté). Je n'ai pas pensé à sa pertinence par rapport au modèle d'usine. C'est une observation nette, en fait.
KChaloux
1
@KChaloux: Bien sûr, si vous utilisiez uniquement le modèle de la méthode d'usine, votre case 0: quux = new Foo(); break;devient case 0: return new Foo();qui est franchement aussi facile à écrire et beaucoup plus facile à lire que{ 0, () => new Foo() }
pdr
@pdr Cela apparaît déjà à quelques endroits dans le code. Il y a probablement une bonne raison de créer une méthode d'usine sur l'objet qui a inspiré cette question, mais je me suis dit que c'était assez intéressant à poser seul.
KChaloux
1
@KChaloux: J'avoue ne pas aimer l'obsession récente de remplacer switch / case par un dictionnaire. Je n'ai pas encore vu de cas où simplifier et isoler le commutateur dans sa propre méthode n'aurait pas été plus efficace.
pdr
@pdr Obsession est un mot fort à utiliser ici. Plus à prendre en compte pour décider comment gérer les mappages ponctuels entre les valeurs. Je suis d'accord que dans les cas où cela se répète, il vaut mieux isoler une méthode de création.
KChaloux
7

C # 4.0 vous donne la Lazy<T>classe, qui est similaire à votre propre deuxième solution, mais crie plus explicitement "l'initialisation paresseuse".

var fooDict = new Dictionary<int, Lazy<IBigObject>>()
{
    { 0, new Lazy(() => new Foo()) }, // Returns a new instance of Foo when invoked
    { 1, new Lazy(() => new Bar()) }, // Ditto Bar
    { 2, new Lazy(() => new Baz()) }  // Ditto Baz
}
Avner Shahar-Kashtan
la source
Ooh, je ne savais pas ça.
KChaloux
Oh c'est gentil!
Laurent Bourgault-Roy
2
Cependant, une fois Lazy.Value invoquée, elle utilise la même instance pour sa durée de vie. Voir Initialisation paresseuse
Dan Lyons
Bien sûr, sinon ce ne serait pas une initialisation paresseuse, juste une réinitialisation à chaque fois.
Avner Shahar-Kashtan
L'OP déclare qu'il en a besoin pour créer de nouvelles instances à chaque fois. La deuxième solution avec lambdas et la troisième solution avec un commutateur le font toutes les deux, tandis que la première solution et l'implémentation Lazy <T> ne le font pas.
Dan Lyons
2

Stylistiquement, je pense que la lisibilité est égale entre eux. Il est plus facile de faire une injection de dépendance avec le Dictionary.

N'oubliez pas que vous devez vérifier si la clé existe lors de l'utilisation de la Dictionary, et devez fournir une solution de repli dans le cas contraire.

Je préférerais la switchdéclaration pour les chemins de code statiques et la Dictionarypour les chemins de code dynamiques (où vous pourriez ajouter ou supprimer des entrées). Le compilateur peut être en mesure d'effectuer certaines optimisations statiques avec le switchqu'il ne peut pas avec le Dictionary.

Fait intéressant, ce Dictionarymodèle est ce que les gens font parfois en Python, car Python n'a pas l' switchinstruction. Sinon, ils utilisent des chaînes if-else.

M. Dudley
la source
1

En général, je ne préférerais ni l'un ni l'autre.

Tout ce qui consomme cela devrait fonctionner avec a Func<int, IBigObject>. Ensuite, la source de votre mappage peut être un dictionnaire ou une méthode qui a une instruction switch ou un appel de service Web ou une recherche de fichier ... peu importe.

En ce qui concerne l'implémentation, je préférerais le dictionnaire car il est plus facilement refactorisé de «dictionnaire de code dur, clé de recherche, résultat de retour» à «charger le dictionnaire à partir du fichier, clé de recherche, résultat de retour».

Telastyn
la source