所以这就是问题的关键: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();
}
答案 0 :(得分:22)
不,这不是线程安全的。
以上的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指令,所以辅助线程可能会导致竞争条件 - 导致返回的对象与一组不同。
通过Lazy<T>
处理延迟实例化会好得多。这提供了一个线程安全的,懒惰的实例化模式。当然,上面的代码没有进行延迟实例化(而是每次>返回一个新对象直到设置_bar
的某个时间),但我怀疑这是一个错误,而不是预期的行为
此外,Lazy<T>
使设置变得困难。
要以线程安全的方式复制上述行为,需要显式同步。
关于您的更新:
Bar属性的getter永远不会返回null。
查看上面的IL,它_bar
(通过ldfld),然后使用brtrue.s检查该对象是否为空。如果对象不为null,则跳转,将_bar
的值从执行堆栈复制到本地stloc.0,然后返回 - 返回_bar
的实际值。
如果_bar
未设置,则会将其从执行堆栈中弹出,然后创建一个新对象,然后存储并返回该对象。
这两种情况都会阻止返回null
值。但是,我一般不会认为这个线程安全,因为调用set的同时调用get可能会导致返回不同的对象,并且它是一个竞争条件,因为它是对象返回实例(设置值或新对象)。
答案 1 :(得分:4)
我不会使用'thread safe'这个词来引用它。相反,我会问这个问题,其中哪一个与null coalesce运算符相同?
get { return _bar != null ? _bar : new Object(); }
或
get
{
Object result = _bar;
if(result == null)
{
result = new Object();
}
return result;
}
从阅读其他回复看,它看起来像编译成等同于第二个,而不是第一个。如你所知,第一个可以返回null,但第二个永远不会。
这个线程安全吗?从技术上讲,没有。阅读_bar
后,另一个线程可以修改_bar
,并且getter将返回一个过时的值。但是从你提出问题的方式来看,我认为这正是你所寻找的。 p>
编辑:这是一种避免整个问题的方法。由于value
是局部变量,因此无法在幕后更改。
public class Foo
{
Object _bar = new Object();
public Object Bar
{
get { return _bar; }
set { _bar = value ?? new Object(); }
}
}
编辑2:
这是我从Release编译中看到的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中,显而易见的是,它使用三元运算符两次读取_bar
字段,但只使用null coalesce和中间结果var读取一次。此外,零合并方法的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(); }
}
答案 2 :(得分:2)
getter永远不会返回null 。
这是因为当对变量(_bar
)执行读取时,将计算表达式,然后生成的对象(或null)将“释放”变量(_bar
)。这是第一次评估的结果,然后“传递”给合并操作员。 (见里德对IL的好答案。)
然而,这不是线程安全的,并且由于与上述相同的原因,分配很容易丢失。
答案 3 :(得分:0)
Reflector说不:
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>();
}
}
在你提到的方面,这似乎不是线程安全的。