私はこの質問をして、この興味深い(そして少し戸惑う)答えを得ました.
ダニエルは、 ECMA-335 CLINullReferenceException
仕様により、コンパイラが次のDoCallback
メソッドからa をスローするコードを生成できる可能性があると回答で述べています (私が間違って読んでいる場合を除きます) 。
class MyClass {
private Action _Callback;
public Action Callback {
get { return _Callback; }
set { _Callback = value; }
}
public void DoCallback() {
Action local;
local = Callback;
if (local == null)
local = new Action(() => { });
local();
}
}
NullReferenceException
彼は、 aがスローされないことを保証するために、volatile
キーワードを で使用する_Callback
かlock
、行の周りで a を使用する必要があると述べていますlocal = Callback;
。
誰かがそれを裏付けることができますか? もしそれが本当なら、この問題に関してMonoコンパイラと.NETコンパイラの動作に違いはありますか?
編集ここに標準
へのリンクがあります。
更新
これは仕様の適切な部分だと思います(12.6.4):
CLI の適合実装は、単一の実行スレッド内で、スレッドによって生成された副作用と例外が CIL によって指定された順序で表示されることを保証する任意のテクノロジを使用して、プログラムを自由に実行できます。この目的のために、揮発性操作 (揮発性読み取りを含む) のみが目に見える副作用を構成します。(揮発性操作のみが目に見える副作用を構成しますが、揮発性操作は不揮発性参照の可視性にも影響することに注意してください。) 揮発性操作は §12.6.7 で指定されています。別のスレッドによってスレッドに注入された例外に関連する順序の保証はありません (このような例外は「非同期例外」と呼ばれることがあります (例: System.Threading.ThreadAbortException))。
[理論的根拠: 最適化コンパイラは、この並べ替えが観察可能なプログラムの動作を変更しない範囲で、副作用と同期例外を自由に並べ替えることができます。終了理由]
[注: CLI の実装は、最適化コンパイラを使用することが許可されています。たとえば、CIL をネイティブ マシン コードに変換するために、コンパイラが (実行の各単一スレッド内で) 同じ順序の副作用と同期例外を維持する場合に限ります。
だから...私は、このステートメントにより、コンパイラがCallback
プロパティ(単純なフィールドにアクセスする)とlocal
変数を最適化して、単一の実行スレッドで同じ動作をする次のものを生成できるかどうかに興味があります:
if (_Callback != null) _Callback();
else new Action(() => { })();
キーワードの 12.6.7 セクションはvolatile
、最適化を回避したいプログラマーに解決策を提供しているようです。
揮発性読み取りには「取得セマンティクス」があります。つまり、読み取りは、CIL 命令シーケンスの読み取り命令の後に発生するメモリへの参照の前に発生することが保証されます。揮発性書き込みには「解放セマンティクス」があります。つまり、書き込みは、CIL 命令シーケンスの書き込み命令の前のメモリ参照の後に発生することが保証されます。CLI の適合実装は、揮発性操作のこのセマンティクスを保証するものとします。これにより、すべてのスレッドが、他のスレッドによって実行された揮発性書き込みを、実行された順序で監視できるようになります。しかし、適合する実装は、実行のすべてのスレッドから見た揮発性書き込みの単一の全体的な順序を提供する必要はありません。