web-dev-qa-db-fra.com

Instruction de commutateur multi-variable en c #

J'aimerais utiliser une instruction switch qui prend plusieurs variables et ressemble à ceci:

switch (intVal1, strVal2, boolVal3)
{
   case 1, "hello", false:
      break;
   case 2, "world", false:
      break;
   case 2, "hello", false:

   etc ....
}

Est-il possible de faire quelque chose comme ça en C #? (Je ne veux pas utiliser d'instructions de commutateur imbriquées pour des raisons évidentes).

19
BanditoBunny

Il n'y a pas de fonctionnalité intégrée permettant de faire cela en C # et je ne connais aucune bibliothèque pour le faire.

Voici une approche alternative, utilisant Tuple et des méthodes d'extension:

using System;

static class CompareTuple {
    public static bool Compare<T1, T2, T3>(this Tuple<T1, T2, T3> value, T1 v1, T2 v2, T3 v3) {
        return value.Item1.Equals(v1) && value.Item2.Equals(v2) && value.Item3.Equals(v3); 
    }
}

class Program {
    static void Main(string[] args) {
        var t = new Tuple<int, int, bool>(1, 2, false);
        if (t.Compare(1, 1, false)) {
            // 1st case
        } else if (t.Compare(1, 2, false)) {
            // 2nd case
        } else { 
            // default
        }
    }
}

En gros, cela ne fait rien de plus que de fournir une syntaxe pratique pour vérifier plusieurs valeurs et d'utiliser plusieurs ifs au lieu d'un commutateur.

10
Paolo Tedesco

Regardons cela d'une autre manière. Si tu as:

  • Très spécifique combinaisons que vous voulez vérifier;
  • Aucune comparaison à faire;
  • Un gestionnaire par défaut pour chaque cas ne correspondant pas;
  • Tous les types primitifs/valeurs (int, bool, string, etc.)

Ensuite, vous pouvez utiliser à la place une table de consultation dont la vitesse d'exécution est similaire à celle de l'instruction switch mais qui n'est pas aussi efficace (car elle doit calculer les hachages). Pourtant, c'est probablement suffisant. Et cela vous donne la possibilité de nommer des cas, de rendre cette explosion combinatoire un peu moins déroutante et impossible à maintenir.

Un exemple de code:

private static readonly Tuple<int, int, bool> NameOfCase1 = 
    Tuple.Create(1, 1, false);
private static readonly Tuple<int, int, bool> NameOfCase2 =
    Tuple.Create(2, 1, false);
private static readonly Tuple<int, int, bool> NameOfCase3 =
    Tuple.Create(2, 2, false);

private static readonly Dictionary<Tuple<int, int, bool>, string> Results =
    new Dictionary<Tuple<int, int, bool>, string>
{
    { NameOfCase1, "Result 1" },
    { NameOfCase2, "Result 2" },
    { NameOfCase3, "Result 3" }
};

public string GetResultForValues(int x, int y, bool b)
{
    const string defaultResult = "Unknown";
    var lookupValue = Tuple.Create(x, y, b);
    string result;
    Results.TryGetValue(lookupValue, out result);
    return defaultResult;
}

Si vous devez réellement exécuter une fonction ou une méthode pour chaque cas, vous pouvez utiliser un type de résultat (valeur de dictionnaire) de Action<T> ou Func<T> à la place.

Notez que j'utilise Tuple<T1,T2,T3> ici car il contient déjà toute la logique du code de hachage. La syntaxe est un peu gênante en C #, mais si vous le souhaitez, vous pouvez implémenter votre propre classe de recherche et remplacer simplement Equals et GetHashCode.

12
Aaronaught

Vous pouvez le faire en C 7 et supérieur avec le mot clé when :

switch (intVal1)
{
    case 1 when strVal2 == "hello" && boolVal3 == false:
        break;
    case 2 when strVal2 == "world" && boolVal3 == false:
        break;
    case 2 when strVal2 == "hello" && boolVal3 == false:
        break;
}
7
Stephen Kennedy

Mon carrément fou prend ça:

class Program
{
    static void Main(string[] args)
    {
        var i = 1;
        var j = 34;
        var k = true;
        Match(i, j, k).
            With(1, 2, false).Do(() => Console.WriteLine("1, 2, 3")).
            With(1, 34, false).Do(() => Console.WriteLine("1, 34, false")).
            With(x => i > 0, x => x < 100, x => x == true).Do(() => Console.WriteLine("1, 34, true"));

    }

    static Matcher<T1, T2, T3> Match<T1, T2, T3>(T1 t1, T2 t2, T3 t3)
    {
        return new Matcher<T1, T2, T3>(t1, t2, t3);
    }
}

public class Matcher<T1, T2, T3>
{
    private readonly object[] values;

    public object[] Values
    {
        get { return values; }
    }

    public Matcher(T1 t1, T2 t2, T3 t3)
    {
        values = new object[] { t1, t2, t3 };
    }

    public Match<T1, T2, T3> With(T1 t1, T2 t2, T3 t3)
    {
        return new Match<T1, T2, T3>(this, new object[] { t1, t2, t3 });
    }

    public Match<T1, T2, T3> With(Func<T1, bool> t1, Func<T2, bool> t2, Func<T3, bool> t3)
    {
        return new Match<T1, T2, T3>(this, t1, t2, t3);
    }
}

public class Match<T1, T2, T3>
{
    private readonly Matcher<T1, T2, T3> matcher;
    private readonly object[] matchedValues;
    private readonly Func<object[], bool> matcherF; 

    public Match(Matcher<T1, T2, T3> matcher, object[] matchedValues)
    {
        this.matcher = matcher;
        this.matchedValues = matchedValues;
    }

    public Match(Matcher<T1, T2, T3> matcher, Func<T1, bool> t1, Func<T2, bool> t2, Func<T3, bool> t3)
    {
        this.matcher = matcher;


        matcherF = objects => t1((T1)objects[0]) && t2((T2)objects[1]) && t3((T3)objects[2]);
    }

    public Matcher<T1, T2, T3> Do(Action a)
    {
        if(matcherF != null && matcherF(matcher.Values) || matcher.Values.SequenceEqual(matchedValues))
            a();

        return matcher;
    }
}
6
Anton Gogolev

Vous pouvez convertir en chaîne:

switch (intVal1.ToString() + strVal2 + boolVal3.ToString())
{
   case "1helloFalse":
      break;
   case "2worldFalse":
      break;
   case "2helloFalse":

   etc ....
}

Je pense cependant que la question qui se pose est de savoir s'il existe ou non un meilleur moyen de définir la logique. Par exemple, disons que vous essayez de comprendre qui sait Superman. Nous pourrions faire le chèque comme ceci:

switch (first + last)
{
   case "ClarkKent":
   case "LoisLane":
      // YES
      break;
   default;
      // Sadly, no
      break;
}

Mais que se passe-t-il lorsque vous rencontrez un autre gars nommé Clark Kent? Vous ne pourriez vraiment pas avoir une autre valeur sur laquelle vous déterminez cette logique, à savoir bool KnowsSuperman?

L’idée étant qu’une instruction switch est utilisée pour déterminer la logique sur la base d’un ensemble unique de choix. Si vous essayez de désactiver plusieurs valeurs, la logique peut devenir incroyablement difficile à maintenir sur toute la ligne.

Un autre exemple serait si vous devez regrouper des personnes en plusieurs groupes et effectuer une logique en fonction du groupe dans lequel ils se trouvent. Vous pouvez le coder de manière à indiquer dans le groupe A, mais que se passe-t-il si vous devez ajouter une autre personne au groupe A? Vous devrez changer le code. Au lieu de cela, vous pouvez créer une propriété supplémentaire appelée Groupe, qui peut être une énumération ou une chaîne, que vous pouvez utiliser pour spécifier le groupe dans lequel se trouve une personne.

4
Mark Synowiec

Mise à jour pour 2018. À partir de C # 7.0, Microsoft a introduit la clause "lorsque" pour les commutateurs, ce qui permet effectivement d'étendre les cas de commutation avec des conditions supplémentaires.

https://docs.Microsoft.com/en-us/dotnet/csharp/language-reference/keywords/switch#the-case-statement-and-the-when- clause

1
Hastaroth

Selon la spécification du langage C #, l'expression switch doit être résolue en sbyte, octet, sbyte, octet, short, ushort, int, uint, long, ulong, char, string ou enum-type . Cela signifie que vous ne pouvez pas activer Tuple ou d'autres types d'ordre supérieur.

Vous pouvez essayer de regrouper les valeurs, en supposant qu'il y ait de la place. Par exemple, supposons que chacun des entiers se trouve dans la plage 0..9.

switch (intVal1 * 100 + intVal2 * 10 + (boolVal3 ? 1 : 0))
{
case 100: /* intVal1 = 1, intVal2 = 0, boolVal3 = false */ ... break;
case 831: /* intVal1 = 8, intVal2 = 3, boolVal3 = true */ ... break;
}
1
Raymond Chen

Vous ne pouvez pas faire cela en C # pour autant que je sache.

Mais vous pouvez le faire à partir de MSDN:

L'exemple suivant montre que le passage d'une étiquette à une autre est autorisé pour les étiquettes de cas vides:

 switch(n) 
        {
            case 1:
            case 2: 
            case 3: 
                Console.WriteLine("It's 1, 2, or 3.");
                break; 
        default: 
            Console.WriteLine("Not sure what it is.");
            break; 
        }
0
JonH
if (a == 1 && b == 1) {}
else if (a == 1 && b == 2) {}
else if (a == 2 && b ==2) {}
0
Joel Martinez

Je fais ce genre de chose avec des listes ou des tableaux. Si vous pouvez énumérer les conditions possibles (ce qui est évidemment le cas si vous souhaitez effectuer un commutateur à valeurs multiples), créez une table de recherche avec une clé à parties multiples et une valeur Action ou Func<T>.

Une version simple utiliserait une Dictionary:

class LookupKey: IComparable<LookupKey>
{
    public int IntValue1 { get; private set; }
    public int IntValue2 { get; private set; }
    public bool BoolValue1 { get; private set; }
    public LookupKey(int i1, int i2, bool b1)
    {
        // assign values here
    }
    public int Compare(LookupKey k1, LookupKey k2)
    {
        return k1.IntValue1 == k2.IntValue1 &&
               k1.IntValue2 == k2.IntValue2 &&
               k1.BoolValue1 == k2.BoolValue1;
    }
    public int GetHashCode()
    {
        return (19 * IntValue1) + (1000003 * IntValue2) + (BoolValue1) ? 0 : 100000073;
    }
    // need to override Equals
}

Et votre dictionnaire:

static readonly Dictionary<LookupKey, Action<object>> LookupTable;

Vous pouvez ensuite renseigner le dictionnaire au démarrage, puis une recherche devient simple:

Action<object> MethodToCall;
if (LookupTable.TryGetValue(new LookupKey(i1, i2, b1), out MethodToCall)
    MethodToCall(theData);
else
    // default action if no match

C'est un peu de code à configurer, mais son exécution est très rapide.

0
Jim Mischel