web-dev-qa-db-ja.com

C#の多変数switchステートメント

いくつかの変数を取り、次のように見えるswitchステートメントを使用したいと思います。

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

   etc ....
}

C#でこのようなことをする方法はありますか? (私は明らかな理由で入れ子のswitch文を使いたくありません)。

25
BanditoBunny

これをC#で実行するための組み込み機能は(ありませんでした)ありません。また、これを実行するためのライブラリーも知りません。

Tupleと拡張メソッドを使用した別の方法を次に示します。

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
        }
    }
}

これは基本的に、複数の値をチェックするための便利な構文を提供し、ifの代わりに複数のswitchsを使用する以外に何もしません。

11
Paolo Tedesco

when キーワードを使用して、C#7以降でこれを行うことができます。

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;
}
27
Stephen Kennedy

これを別の方法で見てみましょう。あなたが持っている場合:

  • チェックする非常に特定の組み合わせ;
  • 実行する比較はありません。
  • 一致しないすべてのケースのデフォルトハンドラ。
  • すべてのプリミティブ/値タイプ(intboolstringなど)

次に、代わりにlook-up tableを使用できます。これは、switchステートメントと実行速度は同じですが、それほど効率的ではありません(ハッシュを計算する必要があるため)。それでも、おそらくそれで十分です。そして、それはあなたにケースに名前を付ける機会を与え、この組み合わせの爆発をやや混乱させず、維持しにくくします。

コード例:

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;
}

ケースごとに実際に関数またはメソッドを実行する必要がある場合は、代わりにAction<T>またはFunc<T>の結果タイプ(辞書値)を使用できます。

ハッシュコードロジックがすべて組み込まれているため、ここではTuple<T1,T2,T3>を使用していることに注意してください。構文はC#では少し扱いに​​くいですが、必要に応じて、独自のルックアップクラスを実装し、EqualsGetHashCode

12
Aaronaught

私の実にクレイジーな話はこれです:

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

複数の入力を切り替えて値を返す場合は、C#8で タプルパターン を使用できます。これは、タプルの形式で複数の値を入力として受け取るパターンマッチングスイッチ式の形式です。入力を使用した例を次に示します。デフォルトの場合の_の使用に注意してください。

string result = (intVal1, strVal2, boolVal3) switch
{
    (1, "hello", false) => "Combination1",
    (2, "world", false) => "Combination2",
    (2, "hello", false) => "Combination3",
    _ => "Default"
};

上記のリンクされたMSDN記事のより具体的な例(rock、paper、scissorsgame)は次のとおりです。

public static string RockPaperScissors(string first, string second)
    => (first, second) switch
    {
        ("rock", "paper") => "rock is covered by paper. Paper wins.",
        ("rock", "scissors") => "rock breaks scissors. Rock wins.",
        ("paper", "rock") => "paper covers rock. Paper wins.",
        ("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
        ("scissors", "rock") => "scissors is broken by rock. Rock wins.",
        ("scissors", "paper") => "scissors cuts paper. Scissors wins.",
        (_, _) => "tie"
    };
4
Stephen Kennedy

あなたは文字列に変換できます:

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

   etc ....
}

しかし、ロジックを定義するより良い方法があるかどうかが問題になると思います。たとえば、スーパーマンを知っている人を見つけようとしているとしましょう。次のようにチェックできます。

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

しかし、クラークケントという名前の別の男がいるとどうなりますか?このロジックに基づいて決定する他の値、つまりbool KnowsSupermanは本当にありませんか?

つまり、switchステートメントは、単一の選択セットに基づいてロジックを決定するために使用されます。オフに切り替えようとしている値が複数ある場合、ロジックを維持するのが非常に難しくなる可能性があります。

別の例は、人々をいくつかのグループにグループ化し、彼らがいるグループに応じていくつかのロジックを実行する必要がある場合です。たとえば、ボブ、ジェフ、ジム、またはサリーの場合、グループAですが、グループAに他のユーザーを追加する必要がある場合はどうでしょうか。コードを変更する必要があります。代わりに、Groupと呼ばれる追加のプロパティを作成できます。これは列挙型または文字列で、誰かがどのグループに属しているかを指定するために使用できます。

4
Mark Synowiec

2018年の更新。C#7.0の時点で、Microsoftはスイッチの「when」句を導入し、追加の条件でスイッチケースを効果的に拡張できるようにしました。

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

2
Hastaroth
//.Net Core 3.1
    class Convertors
    {
        static void Main(string[] args)
        {
            Console.WriteLine(Convertors.ConvertAny("m","cm", 10));
            Console.ReadKey();
        }
        public static double MToCM(double value)
        {
            return value * 100;
        }
        public static double ConvertAny(string srcUOM, string tgtUOM, double value)
        {
            switch (srcUOM.ToLower(), tgtUOM.ToLower())
            {
                case ("m", "cm"): return Convertors.MToCM(value);
                default: throw new NotImplementedException();
            }
        }
    }
1
BazSTR

C#言語仕様に従って、switchステートメント式は sbyte、byte、sbyte、byte、short、ushort、int、uint、long、ulong、char、string、またはenum-type 。つまり、Tupleまたは他の高次の型をオンに切り替えることはできません。

余裕があると仮定して、値をまとめてパックすることもできます。たとえば、各整数が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

私の知る限り、C#でそれを行うことはできません。

しかし、これはMSDNから実行できます。

次のサンプルは、1つのケースラベルから別のケースラベルへのフォールスルーが空のケースラベルで許可されていることを示しています。

 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

私はリストや配列でこの種のことをしています。可能性のある条件を列挙できる場合(複数値の切り替えを行う場合は当然可能です)、マルチパートキーと値としてActionまたはFunc<T>を使用してルックアップテーブルを作成します。 。

単純なバージョンは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
}

そしてあなたの辞書:

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

その後、起動時にディクショナリを入力すると、ルックアップは次の単純な問題になります。

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

設定するのは少しコードですが、実行は非常に高速です。

0
Jim Mischel