24

これが問題の要点です: Foo.Bar は null を返すことができますか? 明確にするために、「_bar」は、null 以外として評価された後、値が返される前に null に設定できますか?

    public class Foo
    {
        Object _bar;
        public Object Bar
        {
            get { return _bar ?? new Object(); }
            set { _bar = value; }
        }
    }

次の get メソッドの使用は安全ではなく、null 値を返す可能性があることはわかっています。

            get { return _bar != null ? _bar : new Object(); }

アップデート:

同じ問題を別の方法で見ると、次の例がより明確になります。

        public static T GetValue<T>(ref T value) where T : class, new()
        {
            return value ?? new T();
        }

GetValue(...) が null を返すことはありますか? 定義によっては、これはスレッドセーフである場合とそうでない場合があります...正しい問題のステートメントは、値に対するアトミック操作であるかどうかを尋ねていると思います... David Yawは、上記の関数が同等であると言って質問を最もよく定義しました以下に:

        public static T GetValue<T>(ref T value) where T : class, new()
        {
            T result = value;
            if (result != null)
                return result;
            else
                return new T();
        }
4

4 に答える 4

23

いいえ、これはスレッドセーフではありません。

上記の IL は次のようにコンパイルされます。

.method public hidebysig specialname instance object get_Bar() cil managed
{
    .maxstack 2
    .locals init (
        [0] object CS$1$0000)
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldfld object ConsoleApplication1.Program/MainClass::_bar
    L_0007: dup 
    L_0008: brtrue.s L_0010
    L_000a: pop 
    L_000b: newobj instance void [mscorlib]System.Object::.ctor()
    L_0010: stloc.0 
    L_0011: br.s L_0013
    L_0013: ldloc.0 
    L_0014: ret 
}

これは効果的にフィールドのロードを行い_bar、その存在をチェックして最後にジャンプします。同期は行われておらず、これは複数の IL 命令であるため、セカンダリ スレッドが競合状態を引き起こし、返されるオブジェクトが 1 つのセットとは異なる可能性があります。

を介して遅延インスタンス化を処理する方がはるかに優れていますLazy<T>。これにより、スレッドセーフで遅延インスタンス化パターンが提供されます。確かに、上記のコードは遅延インスタンス化を行っていません (むしろ、 が設定されるまで毎回新しいオブジェクトを返します_bar) が、これはバグであり、意図した動作ではないと思われます。

また、Lazy<T>設定が難しくなります。

上記の動作をスレッドセーフな方法で複製するには、明示的な同期が必要になります。


あなたの更新に関して:

Bar プロパティのゲッターが null を返すことはありませんでした。

上記の IL を見ると、 (ldfld を介して) brtrue.s_barを使用してそのオブジェクトが null でないかどうかを確認します。オブジェクトが null でない場合は、ジャンプし、stloc.0を介して実行スタックからローカルに値をコピーし、戻ります -実数値で戻ります。_bar_bar

設定されていない場合_barは、実行スタックから取り出され、新しいオブジェクトが作成され、格納されて返されます。

どちらの場合も、null値が返されません。ただし、繰り返しになりますが、一般的にはこのスレッドセーフとは見なしません。set の呼び出しと同時に get の呼び出しが発生すると、異なるオブジェクトが返される可能性があり、どのオブジェクトがどのオブジェクトであるかという競合状態になる可能性があるためです。インスタンスが返されます (設定値、または新しいオブジェクト)。

于 2011-01-06T20:49:28.863 に答える
4

これを参照するために「スレッドセーフ」という言葉は使用しません。代わりに、次のうちどれが null 合体演算子と同じでしょうか?という質問をします。

get { return _bar != null ? _bar : new Object(); }

また

get
{
    Object result = _bar;
    if(result == null)
    {
        result = new Object();
    }
    return result;
}

他の応答を読むと、最初のものではなく、2番目のものと同等にコンパイルされるようです。ご指摘のとおり、最初のものは null を返す可能性がありますが、2 番目のものは決して返されません。

このスレッドは安全ですか? 技術的には、いいえ。を読み取った後_bar、別のスレッドが を変更する可能性が_barあり、ゲッターは古い値を返します。しかし、あなたが質問した方法から、これがあなたが探しているものだと思います。

編集:これは、問題全体を回避する方法です。はローカル変数であるためvalue、裏で変更することはできません。

public class Foo
{
    Object _bar = new Object();
    public Object Bar
    {
        get { return _bar; }
        set { _bar = value ?? new Object(); }
    }
}

編集2:

リリース コンパイルから見た IL と、IL の私の解釈を次に示します。

.method public hidebysig specialname instance object get_Bar_NullCoalesce() cil managed
{
    .maxstack 8
    L_0000: ldarg.0                         // Load argument 0 onto the stack (I don't know what argument 0 is, I don't understand this statement.)
    L_0001: ldfld object CoalesceTest::_bar // Loads the reference to _bar onto the stack.
    L_0006: dup                             // duplicate the value on the stack.
    L_0007: brtrue.s L_000f                 // Jump to L_000f if the value on the stack is non-zero. 
                                            // I believe this consumes the value on the top of the stack, leaving the original result of ldfld as the only thing on the stack.
    L_0009: pop                             // remove the result of ldfld from the stack.
    L_000a: newobj instance void [mscorlib]System.Object::.ctor()
                                            // create a new object, put a reference to it on the stack.
    L_000f: ret                             // return whatever's on the top of the stack.
}

他の方法から私が見ているのは次のとおりです。

.method public hidebysig specialname instance object get_Bar_IntermediateResultVar() cil managed
{
    .maxstack 1
    .locals init (
        [0] object result)
    L_0000: ldarg.0 
    L_0001: ldfld object CoalesceTest::_bar
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: brtrue.s L_0010
    L_000a: newobj instance void [mscorlib]System.Object::.ctor()
    L_000f: stloc.0 
    L_0010: ldloc.0 
    L_0011: ret 
}

.method public hidebysig specialname instance object get_Bar_TrinaryOperator() cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: ldfld object CoalesceTest::_bar
    L_0006: brtrue.s L_000e
    L_0008: newobj instance void [mscorlib]System.Object::.ctor()
    L_000d: ret 
    L_000e: ldarg.0 
    L_000f: ldfld object CoalesceTest::_bar
    L_0014: ret 
}

IL では、3 項演算子でフィールドを 2 回読み取っていることは明らかです_barが、null 合体と中間結果の var では 1 回だけです。さらに、null 合体メソッドの IL は中間結果 var メソッドに非常に近いです。

そして、これらを生成するために使用したソースは次のとおりです。

public object Bar_NullCoalesce
{
    get { return this._bar ?? new Object(); }
}

public object Bar_IntermediateResultVar
{
    get
    {
        object result = this._bar;
        if (result == null) { result = new Object(); }
        return result;
    }
}

public object Bar_TrinaryOperator
{
    get { return this._bar != null ? this._bar : new Object(); }
}
于 2011-01-06T21:06:28.247 に答える
2

ゲッターは決して null を返しません

これは、変数( ) に対して読み取りが実行されると、式が評価され、結果のオブジェクト (または null) が変数( )_barから「解放」されるためです。この最初の評価の結果が合体オペレータに渡されます。(リードのILに対する良い答えを参照してください。)_bar

ただし、これはスレッドセーフではなく、上記と同じ理由で割り当てが簡単に失われる可能性があります。

于 2011-01-06T20:49:15.447 に答える
0

リフレクターはノーと言います:

List<int> l = null;
var x = l ?? new List<int>();

コンパイルすると:

[STAThread]
public static void Main(string[] args)
{
    List<int> list = null;
    if (list == null)
    {
        new List<int>();
    }
}

あなたが言及した点では、これはスレッドセーフではないようです。

于 2011-01-06T20:54:24.113 に答える