33

Nullable暗黙の変換の間の相互作用でいくつかの興味深い動作に遭遇しました。値型からの参照型の暗黙的な変換を提供するNullableと、代わりにコンパイルエラーが予想されるときに、参照型を必要とする関数に型を渡すことができることがわかりました。以下のコードはこれを示しています。

static void Main(string[] args)
{
    PrintCatAge(new Cat(13));
    PrintCatAge(12);
    int? cat = null;
    PrintCatAge(cat);
}

private static void PrintCatAge(Cat cat)
{
    if (cat == null)
        System.Console.WriteLine("What cat?");
    else
        System.Console.WriteLine("The cat's age is {0} years", cat.Age);
}

class Cat
{
    public int Age { get; set; }
    public Cat(int age)
    {
        Age = age;
    }

    public static implicit operator Cat(int i)
    {
        System.Console.WriteLine("Implicit conversion from " + i);
        return new Cat(i);
    }
}

出力:

The cat's age is 13 years
Implicit conversion from 12
The cat's age is 12 years
What cat?

から変換コードを削除Catすると、予期されるエラーが発生します。

Error 3 The best overloaded method match for 'ConsoleApplication2.Program.PrintCatAge(ConsoleApplication2.Program.Cat)' has some invalid arguments

Error 4 Argument 1: cannot convert from 'int?' to 'ConsoleApplication2.Program.Cat

ILSpyで実行可能ファイルを開くと、生成されたコードは次のようになります。

int? num = null;
Program.PrintCatAge(num.HasValue ? num.GetValueOrDefault() : null);

同様の実験で、変換を削除しPrintCatAge、コンパイラが同様の操作を実行するかどうかを確認するためにint(null許容ではない)を必要とするオーバーロードを追加しましたが、実行しません。

私は何が起こっているのか理解していますが、その正当性を理解していません。この振る舞いは私には予想外であり、奇妙に思えます。変換またはのドキュメントでMSDNでこの動作への参照を見つけることに成功しませんでしたNullable<T>

私が提起する質問は、これは意図的なものであり、なぜこれが起こっているのかという説明がありますか?

4

1 に答える 1

29

先ほど、(1)これはコンパイラのバグであり、(2)新しいバグであると述べました。最初のステートメントは正確でした。2つ目は、時間通りにバスに乗ろうと急いで混乱したことです。(私が考えていたバグは、私にとっては新しいものであり、変換の解除とインクリメント演算子の解除を含む、はるかに複雑なバグです。)

これは、長年の既知のコンパイラバグです。Jon Skeetが最初に私に気づいたのは少し前のことで、どこかにStackOverflowの質問があると思います。どこが手に負えないのか思い出せません。おそらくジョンはそうします。

だから、バグ。「持ち上げられた」演算子を定義しましょう。演算子がnull許容値型Sからnull許容値型Tに変換する場合、S?から変換する「リフト」演算子もあります。T?に、ヌルS?null Tに変換しますか?と非ヌルS?Tに変換しますか?Sをアンラップすることによって?Sに変換し、SをTに変換し、TをTにラップしますか?

仕様では、(1)リフトされた演算子が存在する唯一の状況は、SとTが両方ともヌル不可能な値型であり、(2)リフトされた変換演算子とリフトされていない変換演算子の両方がそれらがはコンバージョンの該当する候補であり、両方が該当する場合は、該当するコンバージョンのソースタイプとターゲットタイプ(リフトまたはリフトなし)を使用して、最適なソースタイプ、最適なターゲットタイプ、そして最終的にはすべての該当するコンバージョンの最適なコンバージョンが決定されます。

残念ながら、実装はこれらすべてのルールに完全に違反しており、多くの既存のプログラムを壊さずに変更することはできません。

まず、解雇されたオペレーターの存在に関する規則に違反します。リフトされた演算子は、SとTが両方ともnull許容でない値型である場合、またはSがnull不可能な値型であり、Tがnullを割り当てることができる任意の型である場合、実装によって存在すると見なされます:参照型、null許容値タイプ、またはポインタタイプ。これらすべての場合において、私たちは持ち上げられたオペレーターを生み出します。

あなたの特定のケースでは、nullをチェックすることによってnull許容型を参照型Catに変換すると言うことで、nullableに引き上げます。ソースがnullでない場合、通常どおりに変換します。そうである場合は、nullのCatを生成します。

第2に、該当する候補の1つがリフトされたオペレーターである場合に、該当する候補の最適なソースおよびターゲットタイプを決定する方法に関する規則に完全に違反します。また、どちらが最適な演算子であるかを決定する規則にも違反します。

要するに、それは実際の顧客を壊さずに修正することができない大きな混乱であるため、Roslynでの動作を祀る可能性があります。いつかブログでコンパイラの正確な動作を文書化することを検討しますが、私があなたである場合、その日を待っている間は息を止めません。

そしてもちろん、エラーについて多くの謝罪。

于 2012-04-16T23:24:57.670 に答える