In local variables, with optimization turned on, the compiler will (at least sometimes) compile to code which first assigns to the variable, then calls Add
(or sets properties, for object initializers).
If you use a static or an instance variable, you'll see different behaviour:
class Test
{
static List<int> StaticList = new List<int> { 1 };
List<int> InstanceList = new List<int> { 2 };
}
Gives the following type initializer IL:
.method private hidebysig specialname rtspecialname static
void .cctor() cil managed
{
// Code size 21 (0x15)
.maxstack 2
.locals init (class [mscorlib]System.Collections.Generic.List`1<int32> V_0)
IL_0000: newobj instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: ldc.i4.1
IL_0008: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_000d: nop
IL_000e: ldloc.0
IL_000f: stsfld class [mscorlib]System.Collections.Generic.List`1<int32> Test::StaticList
IL_0014: ret
} // end of method Test::.cctor
And the following constructor IL:
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 29 (0x1d)
.maxstack 3
.locals init (class [mscorlib]System.Collections.Generic.List`1<int32> V_0)
IL_0000: ldarg.0
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldc.i4.2
IL_0009: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_000e: nop
IL_000f: ldloc.0
IL_0010: stfld class [mscorlib]System.Collections.Generic.List`1<int32> Test::InstanceList
IL_0015: ldarg.0
IL_0016: call instance void [mscorlib]System.Object::.ctor()
IL_001b: nop
IL_001c: ret
} // end of method Test::.ctor
In both cases, the collection is populated before the field is set. Now that's not to say that there may not still be memory model issues, but it's not the same as the field being set to refer to an empty collection and then the Add
call being made. From the perspective of the assigning thread, the assignment happens after the Add
.
In general, both object initializer and collection initializer expressions are equivalent to constructing the object using a temporary variable - so in the case where you use it in an assignment, the property setters are all called before the assignment takes place.
However, I don't believe any special guarantees are given around visibility to other threads for object/collection initializers. I would suggest that you imagine what the code would look like if written out "long-hand" according to the specification, and then reason from there.
There are guarantees given for static initializers and constructors - but primarily within the Microsoft implementation of .NET rather than "general" guarantees (e.g. within the C# specification or the ECMA spec).
var
in there? Locals aren't volatile and they never have dots in their names; are you intending that to be a field? – Levey