24

これは、ジェネリック構造体のオペランドを 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 から英語に言い換えられています)。

  1. MyStruct(null 許容ではない) 引数のみを受け取る最初の式は、単純op_Equalityに (の実装operator ==)を呼び出します。

  2. 2 番目の式は、コンパイル時に、各引数をチェックしてHasValue. 両方が等しくない (両方が等しいnull) 場合は、 を返しますtrue。1 つだけ値がある場合は、 を返しますfalse。それ以外の場合は、2 つの値を呼び出しop_Equalityます。

  3. 3 番目の式は、null 許容引数をチェックして、値があるかどうかを確認します。値がない場合は false を返します。それ以外の場合は、 を呼び出しますop_Equality

ここまでは順調ですね。

次のステップ: ジェネリック型でまったく同じことを行います - 型の定義のすべての場所に変更MyStructし、式でそれを変更します。MyStruct<T>MyStruct<int>

3 番目の式はコンパイルされますがInvalidOperationException、次のメッセージとともに実行時例外がスローされます。

演算子 'Equal' のオペランドがメソッド 'op_Equality' のパラメーターと一致しません。

上記のすべての nullable-lifting を使用して、ジェネリック構造体が非ジェネリック構造体とまったく同じように動作することを期待します。

だから私の質問は:

  1. ジェネリック構造体と非ジェネリック構造体に違いがあるのはなぜですか?
  2. この例外の意味は何ですか?
  3. これは C#/.NET のバグですか?

これを再現するための完全なコードは、この gist で入手できます

4

2 に答える 2

22

簡単な答えは次のとおりです。はい、それはバグです。最小限の再現と短い分析を以下に示します。

謝罪いたします。私はそのコードをたくさん書いたので、おそらく私の悪いことでした。

Roslyn の開発、テスト、およびプログラム管理チームに再現を送信しました。これが Roslyn で再現されるとは思えませんが、再現されないことを確認し、これが C# 5 サービス パックの基準になるかどうかを判断します。

問題を追跡したい場合は、connect.microsoft.com でも問題を入力してください。


最小限の再現:

using System;
using System.Linq.Expressions;
struct S<T>
{
    public static bool operator ==(S<T> a, S<T> b) { return false; }
    public static bool operator !=(S<T> a, S<T> b) { return false; }
}
class Program
{
    static void Main()
    {
        Expression<Func<S<int>?, S<int>, bool>> x = (a, b) => a == b;
    }
}

最小限の再現で生成されるコードは、

ParameterExpression pa = Expression.Parameter(typeof(S<int>?), "a");
ParameterExpression pb = Expression.Parameter(typeof(S<int>), "b");
Expression.Lambda<Func<S<int>?, S<int>, bool>>(
    Expression.Equal(pa, pb, false, infoof(S<int>.op_Equality)
    new ParameterExpression[2] { pa, pb } );

指定されたメソッドのinfoofを取得する偽の演算子はどこにありますか。MethodInfo

正しいコードは次のようになります。

ParameterExpression pa = Expression.Parameter(typeof(S<int>?), "a");
ParameterExpression pb = Expression.Parameter(typeof(S<int>), "b");
Expression.Lambda<Func<S<int>?, S<int>, bool>>(
    Expression.Equal(pa, Expression.Convert(pb, typeof(S<int>?), false, infoof(S<int>.op_Equality)
    new ParameterExpression[2] { pa, pb } );

このEqualメソッドは、1 つの null 許容オペランドと 1 つの非 null 許容オペランドを処理できません。両方が null 可能であるか、どちらも null 可能ではないことが必要です。

(が正しいことに注意してくださいfalse。このブール値は、持ち上げられた等式の結果が持ち上げられたブール値であるかどうかを制御します。C# ではそうではなく、VB ではそうです。)

于 2013-05-28T17:27:09.080 に答える
5

はい、このバグは Roslyn (開発中のコンパイラ) ではなくなりました。既存の製品について見ていきます。

于 2013-05-28T22:05:33.350 に答える