これは、ジェネリック構造体のオペランドを null に持ち上げる際のバグのようです。
をオーバーライドする次のダミー構造体を検討してくださいoperator==
。
struct MyStruct
{
private readonly int _value;
public MyStruct(int val) { this._value = val; }
public override bool Equals(object obj) { return false; }
public override int GetHashCode() { return base.GetHashCode(); }
public static bool operator ==(MyStruct a, MyStruct b) { return false; }
public static bool operator !=(MyStruct a, MyStruct b) { return false; }
}
ここで、次の式を検討してください。
Expression<Func<MyStruct, MyStruct, bool>> exprA =
(valueA, valueB) => valueA == valueB;
Expression<Func<MyStruct?, MyStruct?, bool>> exprB =
(nullableValueA, nullableValueB) => nullableValueA == nullableValueB;
Expression<Func<MyStruct?, MyStruct, bool>> exprC =
(nullableValueA, valueB) => nullableValueA == valueB;
3 つすべてが期待どおりにコンパイルおよび実行されます。
それらを ( を使用して) コンパイル.Compile()
すると、次のコードが生成されます (IL から英語に言い換えられています)。
MyStruct
(null 許容ではない) 引数のみを受け取る最初の式は、単純op_Equality
に (の実装operator ==
)を呼び出します。2 番目の式は、コンパイル時に、各引数をチェックして
HasValue
. 両方が等しくない (両方が等しいnull
) 場合は、 を返しますtrue
。1 つだけ値がある場合は、 を返しますfalse
。それ以外の場合は、2 つの値を呼び出しop_Equality
ます。3 番目の式は、null 許容引数をチェックして、値があるかどうかを確認します。値がない場合は false を返します。それ以外の場合は、 を呼び出します
op_Equality
。
ここまでは順調ですね。
次のステップ: ジェネリック型でまったく同じことを行います - 型の定義のすべての場所に変更MyStruct
し、式でそれを変更します。MyStruct<T>
MyStruct<int>
3 番目の式はコンパイルされますがInvalidOperationException
、次のメッセージとともに実行時例外がスローされます。
演算子 'Equal' のオペランドがメソッド 'op_Equality' のパラメーターと一致しません。
上記のすべての nullable-lifting を使用して、ジェネリック構造体が非ジェネリック構造体とまったく同じように動作することを期待します。
だから私の質問は:
- ジェネリック構造体と非ジェネリック構造体に違いがあるのはなぜですか?
- この例外の意味は何ですか?
- これは C#/.NET のバグですか?
これを再現するための完全なコードは、この gist で入手できます。