4

次のコードを Visual Studio 2010 でコンパイルすると:

    public struct A
    {
        public static implicit operator B(A a)
        {
            Console.WriteLine("11111111111");
            return new B();
        }
    }
    public struct B
    { }
    public static B F(A? a)
    {
        return (B)a;
    }

ILSpy を使用すると、return (B)a;実際には としてコンパイルされreturn A.op_Implicit(a.value)ます。

C# 4.0 の第 6.4.5 章「ユーザー定義の明示的な変換」を理解すると、コンパイラ エラーが発生するはずです。

しかし、ECMA 334 の 13.4.4 章「ユーザー定義の明示的な変換」を読むと、上記のコードが準拠しているように見える別の規則があります。

C# 4.0:

適用可能なユーザー定義およびリフトされた変換演算子のセット U を見つけます。このセットは、 S を包含するまたは S によって包含される型から型に変換する D のクラスまたは構造体によって宣言された、ユーザー定義およびリフトされた暗黙的または明示的な変換演算子で構成されます。 U が空の場合、変換は未定義であり、コンパイル時エラーが発生します。

ECMA 334:

適用可能な変換演算子のセット U を見つけます。このセットは、変換する D のクラスまたは構造体によって宣言された、ユーザー定義の、およびS と T が両方とも nullable である場合、解除された暗黙的または明示的な変換演算子 (§13.7.3) で構成されます。 S に含まれる、または S に含まれる型から、T に含まれる、または T に含まれる型へ。 U が空の場合、変換は行われず、コンパイル時エラーが発生します。

VS2010 は C# 4.0 仕様の「ユーザー定義変換の評価」セクションに準拠していませんが、ECMA 仕様には準拠しているというのは正しいですか?

4

1 に答える 1

5

まず、さまざまなルールに従うとどうなるかを見てみましょう。

C# 4.0 仕様の規則に従います。

  • ユーザー定義の変換を検索するタイプのセット D は、A と B で構成されます。
  • 適用可能な変換のセット U は、A から B へのユーザー定義の暗黙的な変換と、A? からの解除されたユーザー定義の暗黙的な変換で構成されます。B? に。
  • ここで、U のこれら 2 つの要素の一意の最良のものを選択する必要があります。
  • 最も具体的なソース タイプは A? です。
  • 最も具体的なターゲット タイプは B です。
  • U には A からの変換が含まれていませんか? Bに、これはあいまいです。

これは理にかなっているはずです。ここでは、A? からの変換であるリフト変換を使用する必要があるかどうかはわかりません。Bに?そしてBから?A から B への変換、または非リフト変換を使用する必要があるかどうか、A から変換しますか? Aへ、そしてAからBへ。


余談:

深く考えてみると、これが違いを生む違いであるかどうかは明らかではありません。

持ち上げられた変換を使用するとします。もし?が非 null の場合、A から変換しますか? A から A、次に A から B、次に B から B?、次に B? B に戻り、成功します。もし?が null の場合、A から変換しますか? null B? に直接接続し、それを B にアンラップするとクラッシュします。

unlifted conversion と A? を使用するとします。null 以外です。次に、A? から変換します。Aへ、AからBへ、完了。もし?が null の場合、A をアンラップするとクラッシュしますか? Aに。

したがって、この場合、変換の両方のバージョンがまったく同じアクションを持っているため、どちらを選択しても問題はなく、あいまいさと呼ぶのは残念です。ただし、これは明らかにコンパイラが C# 4 仕様の文字に従っていないという事実を変えるものではありません。


ECMA仕様はどうですか?

  • セット U は、A から B へのユーザー定義の変換で構成されますが、リフトされた変換ではありません。これは、S (A?) と T (B) の両方が null 可能ではないためです。

そして今、選択できるのは 1 つだけなので、オーバーロードの解決は簡単です。

ただし、これは、コンパイラが ECMA 仕様の規則に従っていることを意味するものではありません。 実際、どちらの仕様の規則にも従っていません。両方の演算子を候補セットに追加しないという点で ECMA 仕様に近いため、この単純なケースでは、候補セットの唯一のメンバーを選択しますしかし実際には、ソースとターゲットの両方が null 許容値型であっても、持ち上げられた演算子が候補セットに追加されることはありません。さらに、より複雑な例で示される他の多くの点で ECMA 仕様に違反しています。

  • 持ち上げられた変換セマンティクス (つまり、メソッドを呼び出す前に null チェックを挿入し、オペランドが null の場合はスキップする) は、null 非許容の構造体型から null 許容の構造体型、ポインター型、または参照型へのユーザー定義の変換で許可されます! つまり、A から文字列への変換がある場合、A から持ち上げられた変換が得られますか? オペランドが null の場合に null 文字列を生成する文字列。このルールは、どちらの仕様にもありません。

  • 仕様によると、相互に包含または包含されなければならない型は、変換される式の型 (仕様では S と呼ばれます) と、ユーザー定義の変換の仮パラメーター型です。C# コンパイラは、変換される式の基になる型が null 許容値型である場合に、その型が包含されているかどうかを実際にチェックします。(仕様では S0。)これは、拒否されるべき特定の変換が代わりに受け入れられることを意味します。

  • 仕様によると、最適なターゲット タイプは、リフトされた、またはリフトされていないさまざまなコンバージョンの出力タイプのセットを調べて決定する必要があります。A から B へのユーザー定義の変換は、最適な出力タイプを見つけるために、B の出力タイプを持つものとして扱う必要があります。しかし、A から B へのキャストがあったとしたら? 次に、コンパイラは実際に B を考慮するでしょうか? 最も具体的な出力タイプを決定する目的で、リフトされていない変換の出力タイプとして!

ユーザー定義の変換処理におけるこれらのバグやその他の多数のバグについて、私は何時間も (何度も何度も...) 続けることができました。ここでは表面をかじっただけです。ジェネリックが関与したときに何が起こるかについては、まだ触れていません。しかし、私はあなたを惜しみません。ここでのポイントは次のとおりです。C# 仕様のどのバージョンも厳密に解析することはできず、そこから複雑なユーザー定義の変換シナリオで何が起こるかを判断することはできません。コンパイラは通常、ユーザーが期待することを実行しますが、通常は間違った理由で実行します。

これは、仕様の最も複雑な部分の 1 つであると同時に、仕様の中でコンパイラが最も準拠していない部分でもあり、これは悪い組み合わせです。これは非常に残念です。

Roslyn を仕様に準拠させるために果敢に試みましたが、失敗しました。そうすることで、あまりにも多くの現実世界の重大な変更が導入されました。代わりに、Roslyn に元のコンパイラの動作をコピーさせ、よりクリーンで理解しやすい実装にしました。

于 2013-08-28T15:26:43.980 に答える