1. ホーム
  2. c#

[解決済み] このスイッチ/パターンマッチングの考え方にメリットはあるのでしょうか?

2022-04-27 02:26:38

質問

最近、F#を見ていて、すぐにフェンスを飛び越えることはないだろうが、C#(またはライブラリサポート)が生活をより簡単にすることができるいくつかの領域を明らかにした。

特に、F#のパターンマッチ機能について考えているのですが、非常に豊富な構文が可能で、現在のswitchやconditionalのC#の同等品よりもはるかに表現力が豊かです。F#のパターンマッチは、現在のC#のswitchやconditionよりもはるかに表現力が豊かです。

  • 型によるマッチング (判別されたユニオンのフルカバレッジチェックを含む) [これはバインドされた変数の型も推測し、メンバアクセスなどを与えることに注意] 。
  • 述語によるマッチング
  • 上記の組み合わせ(および私が知らない他のシナリオもあるかもしれません)

C#が最終的にこの豊富な機能の一部を利用するのは素晴らしいことですが、その間、私は実行時に何ができるかを調べてきました - たとえば、いくつかのオブジェクトを組み合わせて許可するのはかなり簡単です。

var getRentPrice = new Switch<Vehicle, int>()
        .Case<Motorcycle>(bike => 100 + bike.Cylinders * 10) // "bike" here is typed as Motorcycle
        .Case<Bicycle>(30) // returns a constant
        .Case<Car>(car => car.EngineType == EngineType.Diesel, car => 220 + car.Doors * 20)
        .Case<Car>(car => car.EngineType == EngineType.Gasoline, car => 200 + car.Doors * 20)
        .ElseThrow(); // or could use a Default(...) terminator

ここで、getRentPriceはFunc<Vehicle,int>です。

[注意 - たぶん、ここでのSwitch/Caseは間違った用語だと思いますが...アイデアを示しています]。

この方法は、if/elseを繰り返したり、複合三項条件式(三項以外の式では括弧が多くなり、非常に煩雑になる)を使うよりもずっと分かりやすいと私は思います。また ロット 例えば、VB の Select...Case "x To y" のような InRange(...) マッチのような、より特殊なマッチへの簡単な拡張が可能です。

私は、上記のような構成が(言語サポートがない場合)あまり利点があると人々が考えているかどうかを測ろうとしているのです。

なお、私は上記の3つのバリエーションで遊んでいます。

  • Func<TSource,TValue> 評価用バージョン - 複合三項条件文に匹敵するもの
  • Action<TSource> バージョン - if/else if/else if/else に相当するものです。
  • Expression<Func<TSource,TValue>> バージョン - 最初のバージョンと同様ですが、任意の LINQ プロバイダで使用可能です。

さらに、Expressionベースのバージョンを使用すると、Expressionツリーの書き換えが可能になり、繰り返し呼び出すのではなく、すべての分岐を単一の複合条件Expressionにインライン化することができます。最近確認したわけではありませんが、初期のEntity Frameworkのビルドでは、InvocationExpressionがあまり好きではなかったので、これが必要だったように記憶しています。また、LINQ-to-Objectsでは、デリゲートの繰り返し呼び出しを避けるため、より効率的に使用できます。テストでは、上記のようなマッチ(Expressionフォームを使用)は、同等のC#複合条件文と比較して同じ速度(実際には、わずかにより速い)で実行されることを示しています。ただし、Func<...> に基づくバージョンでは、C# の条件文の 4 倍の時間がかかりますが、それでも非常に速く、ほとんどのユースケースで大きなボトルネックになることはないでしょう。

上記について(あるいは C# 言語サポートの充実の可能性について)、ご意見・ご感想・批評などありましたら、ぜひお聞かせください。

どのように解決するのか?

C# 7では、できます。

switch(shape)
{
    case Circle c:
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r:
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default:
        WriteLine("<unknown shape>");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}