Greetings! I am slightly confused about how does C# compiler perform its optimizations.
I have written the following getter to make up "lazy" initialization, and default value in case of null:
Static class Helper:
private static string host;
public static string Host
{
get
{
return host ?? (host= (ConfigurationManager.AppSettings["Host"] ?? "host.ru"));
}
}
Here is the result of disassembling by Reflector:
public static string Host
{
get
{
if (Helper.host == null)
{
string host = Helper.host;
}
return (Helper.host = ConfigurationManager.AppSettings["Host"] ?? "host.ru");
}
}
Looks like it would work in other way than assumed...
UPDATE
private static string host;
public static string Host
{
get
{
return host ?? (host = (GetVal() ?? "default"));
}
}
static void Main(string[] args)
{
Console.WriteLine(Host);
host = "overwritten";
Console.WriteLine(Host);
}
static string GetVal()
{
return "From config";
}
Works correctly (From config, overwritten), but Reflector shows the same:
public static string Host
{
get
{
if (Program.host == null)
{
string host = Program.host;
}
return (Program.host = GetVal() ?? "default");
}
}
This looks like a bug in Reflector's C# disassembly.
Starting with this code:
public static string _test;
public static string _setting;
public static string Test_1
{
get { return _test ?? (_setting ?? "default"); }
}
Reflector shows this C# disassembly:
public static string Test_1
{
get
{
return (_test ?? (_setting ?? "default"));
}
}
and the corresponding IL:
.method public hidebysig specialname static string get_Test_1() cil managed
{
.maxstack 8
L_0000: ldsfld string ConsoleApplication1.Program::_test
L_0005: dup
L_0006: brtrue.s L_0017
L_0008: pop
L_0009: ldsfld string ConsoleApplication1.Program::_setting
L_000e: dup
L_000f: brtrue.s L_0017
L_0011: pop
L_0012: ldstr "default"
L_0017: ret
}
I am not an IL expert, but this is my take on it:
L_0000:
ldsfld
pushes _test
onto the evaluation stackL_0005:
dup
copies the value (_test
) that is topmost on the evaluation stack and pushes that onto the stack.L_0006:
brtrue.s
pops the value created by dup
off the stack and jumps to L_0017
if it is not null
.L_0008:
pop
at this point, _test
is null
, so pop that value off the stack.and it continues to evaluate _setting
in a similar fashion, finally returning "default"
if _setting
is also null
.
Now, if we add an assignment into the code like this:
public static string Test_2
{
get { return _test ?? (_test = (_setting ?? "default")); }
}
Reflector shows this C# disassembly:
public static string Test_2
{
get
{
if (_test == null)
{
string text1 = _test;
}
return (_test = _setting ?? "default");
}
}
which is not correct (if _test
is not null
, instead of returning _test
, it assigns _setting
or "default"
to _test
and then returns).
However, the IL dissassembly looks like the IL for Test_1
, with a couple of extra instructions at L_0017
and L_0018
to do the assignment.
.method public hidebysig specialname static string get_Test_2() cil managed
{
.maxstack 8
L_0000: ldsfld string ConsoleApplication1.Program::_test
L_0005: dup
L_0006: brtrue.s L_001d
L_0008: pop
L_0009: ldsfld string ConsoleApplication1.Program::_setting
L_000e: dup
L_000f: brtrue.s L_0017
L_0011: pop
L_0012: ldstr "default"
L_0017: dup
L_0018: stsfld string ConsoleApplication1.Program::_test
L_001d: ret
}
Finally, if you copy Reflector's C# dissembly and run it against the original, you'll see it produces different results.
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
_test = "Test";
Console.WriteLine(Test_2);
Console.WriteLine(Reflector_Test_2);
Console.ReadLine();
}
public static string _test;
public static string _setting;
public static string Test_1
{
get { return _test ?? (_setting ?? "default"); }
}
public static string Test_2
{
get { return _test ?? (_test = (_setting ?? "default")); }
}
public static string Reflector_Test_2
{
get
{
if (_test == null)
{
string text1 = _test;
}
return (_test = _setting ?? "default");
}
}
}
}
Outputs
Test
default