Question

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");
    }
}
Was it helpful?

Solution

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 stack
  • L_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

OTHER TIPS

I guess I don't understand - both code examples are synonymous.

Remember that Reflector cannot reproduce your exact syntax from the IL that the compiler generates. Sometimes the syntax will be different but the semantics and meaning of the code will always be the same.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top