C#での別のパターンマッチング

最近、C#言語で作業する過程で、多くの現代のマルチパラダイム言語(F#、Scalaなど)に存在するが、C#では使用できないパターンマッチングメカニズムが必要になります。 30分間のグーグルのおかげで見つかった実装( )は、流opinionなインターフェイスを使用して一致表現を構築することを提案しましたが、これはやや面倒な構文です。 2番目の欠点は、実際の使用にはすでにより重要ですが、そのようなマッチャーで「内部」で発生するループ内の述部を列挙するためのオーバーヘッドです。 一般に、2つの基本原則に基づいて、サンプルとの比較の独自の実装を作成しました。





何が起こったのか-カットをお願いします





外観



最初は、マッチャーのデザインを「実際の」デザインのように見せ、メソッドへのチェーン呼び出しを避けたいと思いました。 ペアのリスト「述語-関数」を使用することが決定されました。 短縮リストの初期化構文を使用するために、MatcherクラスはIEnumerableを実装し、Addメソッドも備えています。 使いやすくするため(たとえば、Selectに渡すため)、Func <>への暗黙的なキャストメソッドがクラスに追加されました



使用すると次のようになります。

Func<string, int> match = new Matcher<string, int> { {s => string.IsNullOrEmpty(s), s => 0}, {s => true, s => s.Length} }; int len1 = match(null); // 0 int len2 = match("abc"); // 3
      
      







実装



構文検索のプロセスで記述された最初の実装は「ナイーブ」で、見つかったものと同様に、Matchに渡されたパラメーターを使用して述語の順次実行を実行しました。 コードが最初のポイントで満足し始めたとき(外側がかさばらない)、式<>を使用してマッチャーを書き直しました。



 public class ExprMatcher<TIn, TOut> : IEnumerable<Pair<Expression<Predicate<TIn>>, Expression<Func<TIn, TOut>>>> { private Func<TIn, TOut> _matcher; private Func<TIn, TOut> Matcher { get { return _matcher ?? (_matcher = CompileMatcher()); } } private readonly List<Pair<Expression<Predicate<TIn>>, Expression<Func<TIn, TOut>>>> _caseList = new List<Pair<Expression<Predicate<TIn>>, Expression<Func<TIn, TOut>>>>(); public void Add(Expression<Predicate<TIn>> predicate, Expression<Func<TIn, TOut>> function) { _caseList.Add(new Pair<Expression<Predicate<TIn>>, Expression<Func<TIn, TOut>>>(predicate, function)); } private Func<TIn, TOut> CompileMatcher() { var reverted = Enumerable.Reverse(_caseList).ToList(); var arg = Expression.Parameter(typeof(TIn)); var retVal = Expression.Label(typeof(TOut)); var matcher = Expression.Block( Expression.Throw(Expression.Constant(new MatchException("Provided value was not matched with any case"))), Expression.Label(retVal, Expression.Constant(default(TOut))) ); foreach (var pair in reverted) { retVal = Expression.Label(typeof(TOut)); var condition = Expression.Invoke(pair.First, arg); var action = Expression.Return(retVal, Expression.Invoke(pair.Second, arg)); matcher = Expression.Block( Expression.IfThenElse(condition, action, Expression.Return(retVal, matcher)), Expression.Label(retVal, Expression.Constant(default(TOut))) ); } return Expression.Lambda<Func<TIn, TOut>>(matcher, arg).Compile(); } public TOut Match(TIn value) { return Matcher(value); } public static implicit operator Func<TIn, TOut>(ExprMatcher<TIn, TOut> matcher) { return matcher.Match; } public IEnumerator<Pair<Expression<Predicate<TIn>>, Expression<Func<TIn, TOut>>>> GetEnumerator() { return _caseList.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } }
      
      







Match()メソッドを呼び出すかFuncにキャストすると、引数が述語のいずれも満たさない場合にMatchExceptionをスローする連鎖式が作成されます。 その結果、式のコンパイル時間という形でオーバーヘッドのみが得られます。



代数型



私にC#を使用することのもう1つの欠点は、その中に共用体型がないことです。 それらを追加したかったのですが、同時に(NPEにとって)可能な限り安全に使用するようにしました。

まず、2つのタイプの組み合わせが実装されました。

 public sealed class Union<T1, T2> { public object Value { get; private set; } public T1 Value1 { get; private set; } public T2 Value2 { get; private set; } public Union(T1 value) { Value1 = value; Value = value; } public Union(T2 value) { Value2 = value; Value = value; } public static explicit operator T1(Union<T1, T2> value) { return value.Value1; } public static explicit operator T2(Union<T1, T2> value) { return value.Value2; } public static implicit operator Union<T1, T2>(T1 value) { return new Union<T1, T2>(value); } public static implicit operator Union<T1, T2>(T2 value) { return new Union<T1, T2>(value); } }
      
      





コンストラクターに渡されるパラメーターに応じて、Value1またはValue2がインスタンスで初期化され、Valueも初期化されます。 これにより、値がT1およびT2以外の型をとることを心配することなく、isを使用して述部のValueの型のチェックを比較できます。 t4テンプレートエンジンを使用して、最大17種類のUnionオーバーロードが生成されました。

また、マッチャーの初期化を簡素化するために、MatcherおよびExprMatcherの相続人が作成されました。

 public class ExprMatcher<T1, T2, T3> : ExprMatcher<T1, Union<T2, T3>> {}
      
      







図を完成させるために、かなり些細なオプションも作成されました。



私のマッチャーが誰かに役立つことを願っています:

Bitbucketプロジェクト

Nugetパッケージ



ご清聴ありがとうございました!



All Articles