Generic な型への演算子適用を可能にするユーティリティを書いてみた

コード

本体: SUtils/SUtilsCore/Operators
UnitTest(兼サンプル): OperatorsTest.cs

概要

元ネタにある、ジェネリックな四則演算を可能にするコードに、以下の改良を施したユーティリティを作ってみた。

  • 拡張メソッドにした。
    • operand1.Plus(operand2); といった形でも使える。
  • 一部の演算子だけを持つ型に対応した。
    • 加減算演算子だけ持っていて、乗算が出来ない型、とか。
  • オペランドや戻り値の型がバラバラな演算子にも対応した
    • DateTime + TimeSpan -> DateTime とか。
  • 比較演算子にも対応した。
  • Nullable 型のリフト演算にも対応した。
    • オペランドの片方が Nullable 型で、もう片方が non-Nullable、なども可能

これによって、Generic なクラスなどの中で、型引数で与えられた型のインスタンスについて、

  • +, -
  • *, /, %
  • <, >
  • <=, =>
  • ==, !=

といった演算子を適用することが出来る。

使い方の大筋

using SUtils.Operators;

T1 a;
T1 b;
T2 c;

T1 result1 = a.Plus(b);               // a + b;
T3 result2 = a.Plus<T1, T2, T3>(c);   // a + c; (型が異なる)

// 他にも Minus, Multiply, Divide, Modulo メソッドがある

a.Equal(b);     // a == b;
a.NotEqual(b);  // a != b;

a.LessThan(b);  // a < b;
a.LEq(b);       // a <= b;

中身について

本質的には元ネタの id:ufcpp さんのコードそのままである。id:ufcpp さんのコードに、上に書いた改良を施すための些末な追加などを行っただけであるので、実際の挙動や機構には大して変化が無いはずである。

ちなみに、個人的には

Func<T, T, T> Add = Lambda(Expression.Add);
// ...

Func<T, T, T> Lambda(Binary op){
    return Expression.Lambda<Func<T, T, T>>(op(x, y), new[] { x, y }).Compile();
}

の部分がエレガントだと思ったのであった。

特に、メソッド名(Expression.Add など)からのデリゲートインスタンスの自動生成をうまく使うことで、無駄な記述なく、かつロジックの直交性が確保されている辺り、さりげないが上手い!と思わされたのである。