Étant donné que C # ne peut pas switch
sur un type (qui, je suppose, n'a pas été ajouté en tant que cas spécial car les is
relations signifient que plusieurs distinctes case
peuvent s'appliquer), existe-t-il une meilleure façon de simuler la commutation sur un type autre que celui-ci?
void Foo(object o)
{
if (o is A)
{
((A)o).Hop();
}
else if (o is B)
{
((B)o).Skip();
}
else
{
throw new ArgumentException("Unexpected type: " + o.GetType());
}
}
switch
instruction. Un exemple: l'assemblage A contient un ensemble d'objets de données (qui ne va pas changer, défini dans un document de spécification ou autre). Les assemblages B , C et D font chacun référence à A et fournissent une conversion pour les divers objets de données de A (par exemple, une sérialisation / désérialisation vers un format particulier). Vous devez soit refléter la hiérarchie de classe entière en B , C et D , et utiliser des usines, soit vous avez ...Réponses:
L'activation des types fait définitivement défaut en C # ( MISE À JOUR: dans C # 7 / VS 2017, l'activation des types est prise en charge - voir la réponse de Zachary Yates ci-dessous ). Pour ce faire sans une grande instruction if / else if / else, vous devrez travailler avec une structure différente. J'ai écrit un article de blog il y a un certain temps, détaillant comment construire une structure TypeSwitch.
https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types
Version courte: TypeSwitch est conçu pour empêcher la conversion redondante et donner une syntaxe similaire à une instruction switch / case normale. Par exemple, voici TypeSwitch en action sur un événement de formulaire Windows standard
Le code de TypeSwitch est en fait assez petit et peut facilement être mis dans votre projet.
la source
foreach
(ce qui ne se produirait que si aucune correspondance n'était trouvée)CaseInfo
en vérifiant simplement la valeur de type (si sa valeur null est la valeur par défaut).Avec C # 7 , fourni avec Visual Studio 2017 (version 15. *), vous pouvez utiliser les types dans les
case
instructions (correspondance de modèle):Avec C # 6, vous pouvez utiliser une instruction switch avec l' opérateur nameof () (merci @Joey Adams):
Avec C # 5 et versions antérieures, vous pouvez utiliser une instruction switch, mais vous devrez utiliser une chaîne magique contenant le nom du type ... qui n'est pas particulièrement adaptée aux refactoriseurs (merci @nukefusion)
la source
nameof()
opérateur brillant .case UnauthorizedException _:
Une option consiste à avoir un dictionnaire de
Type
àAction
(ou un autre délégué). Recherchez l'action en fonction du type, puis exécutez-la. Je l'ai déjà utilisé pour les usines.la source
Avec la réponse de JaredPar au fond de ma tête, j'ai écrit une variante de sa
TypeSwitch
classe qui utilise l'inférence de type pour une syntaxe plus agréable:Notez que l'ordre des
Case()
méthodes est important.Obtenez le code complet et commenté pour ma
TypeSwitch
classe . Ceci est une version abrégée de travail:la source
public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource
. Cela vous permet de direvalue.Case((C x) ...
Créez une superclasse (S) et faites-en hériter A et B. Déclarez ensuite une méthode abstraite sur S que chaque sous-classe doit implémenter.
En faisant cela, la méthode "foo" peut également changer sa signature en Foo (S o), ce qui rend le type sûr, et vous n'avez pas besoin de lever cette horrible exception.
la source
Vous pouvez utiliser la correspondance de motifs en C # 7 ou supérieur:
la source
Vous devriez vraiment surcharger votre méthode, ne pas essayer de faire vous-même la désambiguïsation. Jusqu'à présent, la plupart des réponses ne prennent pas en compte les futures sous-classes, ce qui peut entraîner des problèmes de maintenance très graves ultérieurement.
la source
Si vous utilisiez C # 4, vous pourriez utiliser la nouvelle fonctionnalité dynamique pour obtenir une alternative intéressante. Je ne dis pas que c'est mieux, en fait, il semble très probable que ce serait plus lent, mais cela a une certaine élégance.
Et l'utilisation:
La raison pour laquelle cela fonctionne est qu'un appel de méthode dynamique C # 4 a ses surcharges résolues au moment de l'exécution plutôt qu'au moment de la compilation. J'ai écrit un peu plus sur cette idée tout récemment . Encore une fois, je voudrais simplement répéter que cela fonctionne probablement moins bien que toutes les autres suggestions, je le propose simplement par curiosité.
la source
Oui, grâce au C # 7 qui peut être atteint. Voici comment cela se fait (en utilisant un modèle d'expression ):
la source
Pour les types intégrés, vous pouvez utiliser l'énumération TypeCode. Veuillez noter que GetType () est un peu lent, mais probablement pas pertinent dans la plupart des situations.
Pour les types personnalisés, vous pouvez créer votre propre énumération et une interface ou une classe de base avec une propriété ou une méthode abstraite ...
Implémentation de classe abstraite de propriété
Implémentation de classe abstraite de méthode
Implémentation de l'interface de la propriété
Implémentation de l'interface de la méthode
Un de mes collègues vient de m'en parler aussi: cela a l'avantage que vous pouvez l'utiliser pour n'importe quel type d'objet, pas seulement ceux que vous définissez. Il a l'inconvénient d'être un peu plus gros et plus lent.
Définissez d'abord une classe statique comme celle-ci:
Et puis vous pouvez l'utiliser comme ceci:
la source
J'ai aimé l' utilisation par Virtlink du typage implicite pour rendre le commutateur beaucoup plus lisible, mais je n'aimais pas qu'une sortie anticipée ne soit pas possible, et que nous fassions des allocations. Tournons un peu la perf.
Eh bien, ça me fait mal aux doigts. Faisons-le dans T4:
Ajuster un peu l'exemple de Virtlink:
Lisible et rapide. Maintenant, comme tout le monde le souligne dans ses réponses, et compte tenu de la nature de cette question, l'ordre est important dans la correspondance des types. Par conséquent:
la source
Étant donné que l'héritage facilite la reconnaissance d'un objet comme plusieurs types, je pense qu'un commutateur pourrait conduire à une mauvaise ambiguïté. Par exemple:
Cas 1
Cas 2
Parce que s est une chaîne et un objet. Je pense que lorsque vous écrivez un,
switch(foo)
vous vous attendez à ce que foo corresponde à une et une seule descase
déclarations. Avec un commutateur sur les types, l'ordre dans lequel vous écrivez vos instructions case peut éventuellement changer le résultat de l'instruction switch entière. Je pense que ce serait faux.Vous pourriez penser à une vérification du compilateur sur les types d'une instruction "typeswitch", vérifiant que les types énumérés n'héritent pas les uns des autres. Cela n'existe cependant pas.
foo is T
n'est pas le même quefoo.GetType() == typeof(T)
!!la source
Je le ferais
la source
Une autre façon serait de définir une interface IThing puis de l'implémenter dans les deux classes, voici le snipet:
la source
Selon la spécification C # 7.0, vous pouvez déclarer une variable locale de portée dans a
case
de aswitch
:C'est la meilleure façon de faire une telle chose car elle implique simplement des opérations de transtypage et de push-on-the-stack, qui sont les opérations les plus rapides qu'un interprète peut exécuter juste après des opérations et des
boolean
conditions au niveau du bit .En comparant cela à un
Dictionary<K, V>
, voici beaucoup moins d'utilisation de la mémoire: la tenue d'un dictionnaire nécessite plus d'espace dans la RAM et un peu plus de calcul par le CPU pour créer deux tableaux (un pour les clés et l'autre pour les valeurs) et rassembler les codes de hachage pour les clés à mettre valeurs à leurs clés respectives.Donc, pour autant que je sache, je ne pense pas qu'un moyen plus rapide puisse exister à moins que vous ne vouliez utiliser qu'un bloc
if
-then
-else
avec l'is
opérateur comme suit:la source
Vous pouvez créer des méthodes surchargées:
Et transformez l'argument en
dynamic
type afin de contourner la vérification de type statique:la source
Les améliorations C # 8 de la correspondance de motifs ont permis de le faire comme ceci. Dans certains cas, il fait le travail et est plus concis.
la source
Vous recherchez les
Discriminated Unions
fonctionnalités linguistiques de F #, mais vous pouvez obtenir un effet similaire en utilisant une bibliothèque que j'ai créée, appelée OneOfhttps://github.com/mcintyre321/OneOf
L'avantage majeur par rapport à
switch
(etif
etexceptions as control flow
) est qu'il est sûr au moment de la compilation - il n'y a pas de gestionnaire par défaut ou de chuteSi vous ajoutez un troisième élément à o, vous obtiendrez une erreur de compilation car vous devez ajouter un gestionnaire Func dans l'appel de commutateur.
Vous pouvez également faire un
.Match
qui renvoie une valeur, plutôt que d'exécuter une instruction:la source
Créez une interface
IFooable
, puis créez vos classesA
etB
pour implémenter une méthode commune, qui à son tour appelle la méthode correspondante que vous souhaitez:Notez qu'il vaut mieux utiliser à la
as
place d'abord vérifieris
puis lancer, car de cette façon vous faites 2 lancers, donc c'est plus cher.la source
Dans de tels cas, je me retrouve généralement avec une liste de prédicats et d'actions. Quelque chose dans ce sens:
la source
Après avoir comparé les options quelques réponses fournies ici aux fonctionnalités F #, j'ai découvert que F # avait un meilleur support pour la commutation basée sur le type (bien que je m'en tienne toujours à C #).
Vous voudrez peut-être voir ici et ici .
la source
Je créerais une interface avec n'importe quel nom et nom de méthode qui aurait du sens pour votre commutateur, appelons-les respectivement:
IDoable
cela indique de mettre en œuvrevoid Do()
.et changez la méthode comme suit:
Au moins avec cela, vous êtes en sécurité au moment de la compilation et je soupçonne que sur le plan des performances, c'est mieux que de vérifier le type au moment de l'exécution.
la source
Avec C # 8, vous pouvez le rendre encore plus concis avec le nouveau commutateur. Et avec l'utilisation de l'option de suppression _, vous pouvez éviter de créer des variables inutiles lorsque vous n'en avez pas besoin, comme ceci:
Invoice et ShippingList sont des classes et le document est un objet qui peut être l'un ou l'autre.
la source
Je suis d'accord avec Jon pour avoir un hachage d'actions au nom de la classe. Si vous conservez votre modèle, vous voudrez peut-être envisager d'utiliser la construction "as" à la place:
La différence est que lorsque vous utilisez le motif si (foo est Bar) {((Bar) foo) .Action (); } vous effectuez deux fois le casting de caractères. Maintenant, peut-être que le compilateur optimisera et ne fera ce travail qu'une seule fois - mais je ne compterais pas là-dessus.
la source
Comme le suggère Pablo, l'approche par interface est presque toujours la bonne chose à faire pour gérer cela. Pour vraiment utiliser switch, une autre alternative est d'avoir une énumération personnalisée indiquant votre type dans vos classes.
Ceci est également implémenté dans BCL. Un exemple est MemberInfo.MemberTypes , un autre est
GetTypeCode
pour les types primitifs, comme:la source
Il s'agit d'une réponse alternative qui mélange les contributions des réponses JaredPar et VirtLink, avec les contraintes suivantes:
Usage:
Code:
la source
Oui - utilisez simplement la "correspondance de motifs" légèrement bizarre à partir de C # 7 pour faire correspondre la classe ou la structure:
la source
j'utilise
la source
Devrait fonctionner avec
comme:
la source
Si vous connaissez la classe que vous attendez mais que vous n'avez toujours pas d'objet, vous pouvez même le faire:
la source