web-dev-qa-db-ja.com

switchステートメントでc#タプル値タイプを使用する方法

.net4.7で新しいタプル値タイプを使用しています。この例では、タプルの1つ以上のケースに対してswitchステートメントを作成しようとしています。

using System;
namespace ValueTupleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            (char letterA, char letterB) _test = ('A','B');
            Console.WriteLine($"Letter A: '{_test.letterA}', Letter B: '{_test.letterB}'");

            switch (_test)
            {
                case ('A', 'B'):
                    Console.WriteLine("Case ok.");
                    break;
            }

        }
    }
}

残念ながら、これはコンパイルされません。

タプルを取得してswitchステートメントでケースを正しく作成するにはどうすればよいですか?

8
SoftAllan

技術的に質問に答えると、whenを使用してタプルの値を確認できます。

(char letterA, char letterB) _test = ('A', 'B');
Console.WriteLine($"Letter A: '{_test.letterA}', Letter B: '{_test.letterB}'");

switch (_test)
{
    case var Tuple when Tuple.letterA == 'A' && Tuple.letterB == 'B':
        Console.WriteLine("Case ok.");
        break;
    case var Tuple when Tuple.letterA == 'D' && Tuple.letterB == '\0':
        Console.WriteLine("Case ok.");
        break;
}

ただし、ifバージョンの使用を検討してください。これは、より読みやすく理解しやすいソリューションである可能性があるためです。

この質問のもう1つの側面は、単一責任です。あなたのメソッドは、ABD\0文字は、単一責任の原則に違反することを意味します。
OOPの観点から、この知識をメインコードから別のメソッドに分離することをお勧めします。
そのようなものはコードを少しきれいにすることができます:

private static bool IsCaseOk(char a, char b) 
{
    return (a == 'A' && b == 'B') || (a == 'D' && b == '\0'); // any logic here
}

public static void Main() 
{
    (char letterA, char letterB) _test = ('A', 'B');
    Console.WriteLine($"Letter A: '{_test.letterA}', Letter B: '{_test.letterB}'");

    if (IsCaseOk(_test.letterA, _test.letterB)) {
        Console.WriteLine("Case ok.");
    } else {
        Console.WriteLine("Case not ok.");
    }
}

これらの文字がドメイン内で何らかの意味を持つ場合は、2つのcharプロパティを持つクラスを作成し、そこにこのロジックをカプセル化することをお勧めします。

C#7.3ではタプルの同等性が導入されています。これは、質問の最初のアイデアがほぼ正しいことを意味します。比較している値を次のようにキャプチャする必要があります。

var _test = ('A','B');
switch (_test)
{
   case var t when t == ('A', 'B'):
   Console.WriteLine("Case ok.");
   break;
}
8
Ryan

タプルやパターンマッチングを使用しても問題はありません。どちらかといえば、これらにより、よりクリーンなコードを記述し、ロジックを複数のメソッドに分散させることを回避できます。

C#7では、タプル値との照合は許可されていませんまだ。 2つのタプルを==演算子と比較することもできません。 can実行するのは、Equals2つを使用して2つの値タプルを比較することです。

 if (_test.Equals(('A','B'))
{
    Console.WriteLine("Case A ok.");
}
else if (_test.Equals(('D','\0'))
{
    Console.WriteLine("Case D ok.");
}

特定のパターンに一致するパーサー(?)の状態一致を作成しようとしているように見えます。これcanすべての場合に単一のタプルを使用する代わりに、異なる状態クラスを指定すると、パターンマッチングで機能します。

あなたがする必要があるのは、メソッドなしで単一のIStateインターフェースを指定し、それをすべての状態クラスで使用することです。

interface IMyState {};
public class StateA:IMyState{ public string PropA{get;set;} };
public class StateD:IMyState{ public string PropD{get;set;} };

...
IMyState _test= new StateD(...);

switch (_test)
{
    case StateA a: 
        Console.WriteLine($"Case A ok. {a.PropA}");
        break;
    case StateD d: 
        Console.WriteLine($"Case D ok. {d.PropD}");
        break;
    default :
        throw new InvalidOperationException("Where's my state ?");
}

ad変数は強く型付けされています。つまり、IStateインターフェースに何も追加する必要はありません。コンパイラを満足させるためだけにあります。

状態タイプのクラスの代わりに構造体を使用することで、タプルの場合と同じメモリの利点が得られます。分解を使用する場合は、各型にDeconstructメソッドを追加するか、個別の静的クラスでDeconstruct拡張メソッドを使用できます。

5

返信ありがとうございます。

Switchステートメントの使用をやめて、古いif/elseステートメントを使用することにしました。

using System;

namespace ValueTupleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            (char letterA, char letterB) _test = ('A','B');
            Console.WriteLine($"Letter A: '{_test.letterA}', Letter B: '{_test.letterB}'");

            if (_test.letterA == 'A' && _test.letterB == 'B')
            {
                Console.WriteLine("Case A ok.");
            }
            else if (_test.letterA == 'D' && _test.letterB == '\0')
            {
                Console.WriteLine("Case D ok.");
            }

        }
    }
}

このようにして、タプル内のすべての値を必要な順序でテストするかどうかを決定できます。パフォーマンスに大きな違いはないと思います。

Switchステートメントでタプルを使用する別の方法がある場合は、例を挙げてください。

2
SoftAllan

case (...):の構文は、将来の種類のパターン用に予約されています。 C#言語機能仕様で説明されている位置パターンを参照してください: https://github.com/dotnet/csharplang/blob/master/proposals/patterns.md#positional-pattern

1
Julien Couvreur