Considering the following struct :
struct S
{
public string s;
}
What is the difference between 1 :
S instance = new S();
instance.s = "foo";
and 2 :
S instance;
instance.s = "foo";
Both versions compile and run fine.
I am just curious to know what happens behind the scenes.
Edit :
I guess in case 2 S is unassigned until we put a value on its s field;
As this doesn’t work :
S instance;
if (inst.s == null)
inst.s = "foo"; //Compiler drops : Use of possibly unassigned field 's'
while this does :
S instance;
inst.s = "foo";
if (inst.s == null)
inst.s = "bar"; //Compiler drops : Use of possibly unassigned field 's'
and this also works :
S inst = new S();
if (inst.s == null)
inst.s = "foo";
I welcome any deeper explanations about this behavior
Update
I found those 2 posts, completing Marc’s answer :
why are mutable structs evil
when to use struct in c#
As Marc correctly points out, both are equally bad; the right thing to do is to make an immutable struct that takes the string in its constructor.
And as Marc correctly points out, functionally there is no difference.
However, that does not answer the question that you actually asked, which is "what happens behind the scenes?" By "behind the scenes" I’m assuming that you’re talking about the compiler’s semantic analysis of the code, as described in the C# specification.
Fortunately the specification is extremely clear on the difference between these two cases.
First off, as you correctly note, in the first case the variable is considered to be definitely assigned after the first statement. In the second case the variable is not considered to be definitely assigned until after the second statement.
However, the definite assignment analysis is simply a consequence of the actual meaning of the code, which is as follows.
The first fragment:
instanceinstanceinstanceis definitely assigned because all its bits were copied from another valueinstanceThe second fragment
instanceinstanceinstanceis definitely assigned because all its fields have been assignedThe compiler is permitted to notice that there is no way to determine whether or not a temporary was created, initialized and copied. If it does determine that then it is permitted to elide the creation of the temporary, and generate the same code for both fragments. The compiler is not required to do so; this is an optimization and optimizations are never required.
Now, you might wonder under what circumstances you can determine that a temporary was created, initialized and copied. If you do wonder that then read my article on the subject:
https://ericlippert.com/2010/10/11/debunking-another-myth-about-value-types/