27

以下の場合、InvalidCastException がスローされるのはなぜですか? バグの範囲外である理由がわかりません (これは x86 にあります。x64 は clrjit.dll で 0xC0000005 でクラッシュします)。

class Program
{
    static void Main(string[] args)
    {
        MyDouble? my = new MyDouble(1.0);
        Boolean compare = my == 0.0;
    }

    struct MyDouble
    {
        Double? _value;

        public MyDouble(Double value)
        {
            _value = value;
        }

        public static implicit operator Double(MyDouble value)
        {
            if (value._value.HasValue)
            {
                return value._value.Value;
            }

            throw new InvalidCastException("MyDouble value cannot convert to System.Double: no value present.");
        }
    }
}

に対して生成された CIL は次のMain()とおりです。

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 3
    .locals init (
        [0] valuetype [mscorlib]System.Nullable`1<valuetype Program/MyDouble> my,
        [1] bool compare,
        [2] valuetype [mscorlib]System.Nullable`1<valuetype Program/MyDouble> CS$0$0000,
        [3] valuetype [mscorlib]System.Nullable`1<float64> CS$0$0001)
    L_0000: nop 
    L_0001: ldloca.s my
    L_0003: ldc.r8 1
    L_000c: newobj instance void Program/MyDouble::.ctor(float64)
    L_0011: call instance void [mscorlib]System.Nullable`1<valuetype Program/MyDouble>::.ctor(!0)
    L_0016: nop 
    L_0017: ldloc.0 
    L_0018: stloc.2 
    L_0019: ldloca.s CS$0$0000
    L_001b: call instance bool [mscorlib]System.Nullable`1<valuetype Program/MyDouble>::get_HasValue()
    L_0020: brtrue.s L_002d
    L_0022: ldloca.s CS$0$0001
    L_0024: initobj [mscorlib]System.Nullable`1<float64>
    L_002a: ldloc.3 
    L_002b: br.s L_003e
    L_002d: ldloca.s CS$0$0000
    L_002f: call instance !0 [mscorlib]System.Nullable`1<valuetype Program/MyDouble>::GetValueOrDefault()
    L_0034: call float64 Program/MyDouble::op_Implicit(valuetype Program/MyDouble)
    L_0039: newobj instance void [mscorlib]System.Nullable`1<float64>::.ctor(!0)
    L_003e: stloc.3 
    L_003f: ldloca.s CS$0$0001
    L_0041: call instance !0 [mscorlib]System.Nullable`1<float64>::GetValueOrDefault()
    L_0046: call float64 Program/MyDouble::op_Implicit(valuetype Program/MyDouble)
    L_004b: conv.r8 
    L_004c: ldc.r8 0
    L_0055: bne.un.s L_0060
    L_0057: ldloca.s CS$0$0001
    L_0059: call instance bool [mscorlib]System.Nullable`1<float64>::get_HasValue()
    L_005e: br.s L_0061
    L_0060: ldc.i4.0 
    L_0061: stloc.1 
    L_0062: ret 
}

IL の 0x2D ~ 0x3E 行に注意してください。MyDouble?インスタンスを取得し、GetValueOrDefaultそれを呼び出し、その上で暗黙的な演算子を呼び出し、結果を a にラップしてDouble?、コンパイラが生成したCS$0$0001ローカルに格納します。行 0x3F から 0x55 でCS$0$0001値を取得し、'unwrap' 経由GetValueOrDefaultで 0 と比較します...しかし、ちょっと待ってください! MyDouble::op_Implicit行 0x46 で実行する余分な呼び出しは何ですか?

C# プログラムをデバッグすると、確かに への 2 つの呼び出しが表示されます。 が初期化されていないimplicit operator Double(MyDouble value)ため、失敗したのは 2 番目の呼び出しです。value

ここで何が起こっているのですか?

4

2 に答える 2

44

これは明らかにC#コンパイラのバグです。私の注意を引いてくれてありがとう。

ちなみに、例外をスローするユーザー定義の暗黙的な変換演算子を使用することはお勧めできません。ドキュメントには、暗黙の変換は決してスローされないものでなければならないと記載されています。これを明示的な変換にしたくないですか?

とにかく、バグに戻ります。

バグはC#3と4で再現されますが、C#2では再現されません。これは私のせいです。式ツリーのラムダで機能させるために、ユーザー定義のリフトされた暗黙の演算子コードをやり直したときに、おそらくバグが発生しました。申し訳ありません!そのコードは非常にトリッキーで、どうやら私はそれを適切にテストしていませんでした。

コードが行うことになっていることは次のとおりです。

まず、過負荷解決は==の意味を解決しようとします。両方の引数が有効である最良の==演算子は、2つのnull許容doubleを比較するリフトされた演算子です。したがって、次のように分析する必要があります。

Boolean compare = (double?)my == (double?)0.0; 

(このようなコードを書くと、C#3と4で正しいことをします。)

持ち上げられた==演算子の意味は次のとおりです。

  • 両方の引数を評価する
  • 両方がnullの場合、結果はtrueになります。この場合、明らかにこれは発生しません。
  • 一方がnullで、もう一方がnullでない場合、結果はfalseになります。
  • 両方がnullでない場合、両方がdoubleにラップ解除され、doubleとして比較されます。

さて、問題は「左側を評価する正しい方法は何ですか?」です。

ここに、MyDoubleから持ち上げられたユーザー定義の変換演算子がありますか?倍増する?正しい動作は次のとおりです。

  • 「my」がnullの場合、結果はnull double?になります。
  • 「my」がnullでない場合、結果はmy.Valueからdoubleへのユーザー定義の変換であり、次にそのdo​​ubleからdoubleへの変換ですか。

明らかに、このプロセスで問題が発生しています。

データベースにバグを入力しますが、修正を加えると、次のサービスパックに反映される変更の期限を逃してしまう可能性があります。私があなただったら、回避策を検討しているでしょう。繰り返しになりますが、エラーについてお詫び申し上げます。

于 2011-03-04T16:40:01.000 に答える
6

これは確かにコンパイラのバグのように見えます。IL は、コンパイラが MyDouble を変換するコードを生成していることを示唆していますか? 変換演算子を使用して double に変換し、次に double? に変換します。しかし、その double? で変換演算子を再度使用すると、急降下します。それは悪い、間違った引数の型です。その必要もありません。

このフィードバック記事は、このバグに似ています。すでに 6 年以上経過していますが、これはコンパイラのトリッキーな部分に違いありません。だと想像します。

于 2011-03-04T02:31:08.230 に答える