[解決済み] NULL-COALESCING 演算子のカスタム暗黙変換の不思議な挙動
質問
注:これは、以下のように修正されたようです。 ロスリン
への回答を書いていて、この疑問がわいた。 これ の連想性について述べています。 ヌルコアレスティング演算子 .
注意点として、null-coalescing演算子の考え方は、以下のような形の式になります。
x ?? y
を最初に評価します。
x
, その後
-
の値が
x
がヌルである場合。y
が評価され、それが式の最終結果である。 -
の値が
x
が非NULLの場合。y
は ない の値が評価されx
のコンパイル時の型に変換された後の、式の最終結果です。y
必要であれば
現在
通常
変換の必要がないか、あるいは単に null 可能な型から null 不可能な型への変換です。
int?
から
int
. しかし、あなたは
できる
暗黙の変換演算子を独自に作成し、必要に応じて使用する。
単純なケースとして
x ?? y
ということで、特におかしな挙動は見られませんでした。しかし
(x ?? y) ?? z
混乱するような挙動が見られます。
以下は短いですが完全なテストプログラムです。結果はコメントにあります。
using System;
public struct A
{
public static implicit operator B(A input)
{
Console.WriteLine("A to B");
return new B();
}
public static implicit operator C(A input)
{
Console.WriteLine("A to C");
return new C();
}
}
public struct B
{
public static implicit operator C(B input)
{
Console.WriteLine("B to C");
return new C();
}
}
public struct C {}
class Test
{
static void Main()
{
A? x = new A();
B? y = new B();
C? z = new C();
C zNotNull = new C();
Console.WriteLine("First case");
// This prints
// A to B
// A to B
// B to C
C? first = (x ?? y) ?? z;
Console.WriteLine("Second case");
// This prints
// A to B
// B to C
var tmp = x ?? y;
C? second = tmp ?? z;
Console.WriteLine("Third case");
// This prints
// A to B
// B to C
C? third = (x ?? y) ?? zNotNull;
}
}
というわけで、3つのカスタムバリューのタイプができました。
A
,
B
と
C
を、AからB、AからC、BからCに変換しています。
2つ目のケースも3つ目のケースも理解できる...が なぜ は、最初のケースで余分なA-B変換があるのでしょうか?特に、私は 本当に 最初のケースと2番目のケースは同じものであることを期待していました。
何が起こっているのか、誰かわかる人はいますか?C#コンパイラーに関しては、バグと叫ぶのは非常にためらわれるのですが、何が起こっているのかわからずに困っています...。
EDIT: さて、configuratorの回答のおかげで、これがバグだと思う根拠がさらに増えました。EDIT: このサンプルでは、2つのnull-coalescing演算子さえ必要ありません...
using System;
public struct A
{
public static implicit operator int(A input)
{
Console.WriteLine("A to int");
return 10;
}
}
class Test
{
static A? Foo()
{
Console.WriteLine("Foo() called");
return new A();
}
static void Main()
{
int? y = 10;
int? result = Foo() ?? y;
}
}
という出力になります。
Foo() called
Foo() called
A to int
というのは
Foo()
が2回呼ばれるのは、私にとって非常に驚くべきことです。
評価
を2回実行します。
解決方法は?
この問題の解析にご協力いただいた皆様、ありがとうございました。これは明らかにコンパイラのバグです。合体演算子の左辺に2つのNULL可能な型を含む昇格変換がある場合にのみ発生するようです。
具体的にどこが悪いのかはまだ特定できていませんが、コンパイルの "nullable lowering" の段階(初期解析後、コード生成前)のある時点で、以下の式を削減します。
result = Foo() ?? y;
を上の例からモラルに相当するものに変更する。
A? temp = Foo();
result = temp.HasValue ?
new int?(A.op_implicit(Foo().Value)) :
y;
明らかに誤りです。正しい下げ方は
result = temp.HasValue ?
new int?(A.op_implicit(temp.Value)) :
y;
ここまでの分析から推測すると、NULL可能なオプティマイザがここでレールから外れているのだと思います。nullableオプティマイザは、nullable型の特定の式がnullであるはずがないと分かっている状況を探すためにあります。次のような素朴な分析を考えてみよう。
result = Foo() ?? y;
と同じです。
A? temp = Foo();
result = temp.HasValue ?
(int?) temp :
y;
と言うかもしれません。
conversionResult = (int?) temp
と同じです。
A? temp2 = temp;
conversionResult = temp2.HasValue ?
new int?(op_Implicit(temp2.Value)) :
(int?) null
しかし、オプティマイザが介入して、「ちょっと待てよ、tempがNULLでないことはすでにチェックしたんだ。 私たちは、それを最適化して、単に
new int?(op_Implicit(temp2.Value))
私の推測では、最適化されたフォームの
(int?)Foo()
は
new int?(op_implicit(Foo().Value))
Foo()-replaced-with-temporary-and-then-converted の最適化された形が必要なのです。
C#コンパイラの多くのバグは、不適切なキャッシュ処理の判断の結果である。賢者の言葉 後で使うためにファクトをキャッシュするたびに、関連する何かが変更された場合に、潜在的に矛盾を生み出していることになります。 . この場合、最初の分析後に変更された関連事項は、Foo()への呼び出しは常に一時的なフェッチとして実現されるべきであるということです。
C# 3.0では、Nullableの書き換えパスの再編成を多く行いました。このバグはC# 3.0と4.0では再現するが、C# 2.0では再現しないので、おそらく私のミスであることがわかる。申し訳ありません。
データベースにバグを入力してもらい、今後の言語のバージョンアップで修正できるかどうか確認します。皆さん、本当にありがとうございました。
UPDATE: Roslyn用にnullableオプティマイザを一から書き直しました。より良い仕事をし、この種の奇妙なエラーを回避できるようになりました。Roslynのオプティマイザがどのように動作するかについての考察は、ここから始まる連載記事をご覧ください。 https://ericlippert.com/2012/12/20/nullable-micro-optimizations-part-one/
関連
-
[解決済み】ASP.NET Core Dependency Injectionのエラーです。アクティブ化しようとしているときに、タイプのサービスを解決できません。
-
[解決済み】リソースの読み込みに失敗した:ステータス500(内部サーバーエラー)のサーバーの応答)
-
[解決済み] [Solved] 不正な文字列値: '\xEFxBFxBD' for column
-
[解決済み】エラー「必要なフォーマルパラメータに対応する引数が与えられていない」を解決する?
-
[解決済み】WebResource.axdとは何ですか?
-
[解決済み】データが存在しないのに読み込もうとする試みが無効である
-
[解決済み] JavaScriptに「NULL合体」演算子はありますか?
-
[解決済み] PHPの三項演算子とNULL合体演算子の比較
-
[解決済み] C#のnull-coalescing演算子に相当するPythonの演算子はありますか?
-
[解決済み] なぜis演算子はnullを与えるとfalseを返すのですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】「未割り当てのローカル変数を使用」とはどういう意味ですか?
-
[解決済み】Ajax処理で「無効なJSONプリミティブ」と表示される件
-
解決済み] Critical error detected c0000374 - C++ dll returns pointer off allocated memory to C# [解決済み] Critical error detected c0000374 - C++ dll returns pointer off allocated memory to C#.
-
[解決済み】Socket.Selectがエラー "An operation was attempted on something that is not a socket" を返す。
-
[解決済み】Moqを使用してメソッド呼び出しを検証する
-
[解決済み】OnCollisionEnter2Dが実行されない?
-
[解決済み】C#のequal to演算子でtextとvarcharのデータ型は互換性がない
-
[解決済み] 2つのリストを結合する
-
[解決済み】Unityでゲームオブジェクトのすべての子をループスルーして破壊する方法?
-
[解決済み】データが存在しないのに読み込もうとする試みが無効である