C# 编译优化:空合并运算符 - 更新 - Reflector 的 bug?

发布于 2024-10-04 12:28:46 字数 1357 浏览 8 评论 0原文

问候!我对 C# 编译器如何执行优化有点困惑。
我编写了以下 getter 来弥补“惰性”初始化,以及 null 情况下的默认值:

静态类助手:

private static string host;  
public static string Host  
{        
    get  
    {  
        return host ?? (host= (ConfigurationManager.AppSettings["Host"] ?? "host.ru"));  
    }  
}

是 Reflector 反汇编的结果:

public static string Host 
{  
    get  
    {  
        if (Helper.host == null)  
        {  
            string host = Helper.host;  
        }  
        return (Helper.host = ConfigurationManager.AppSettings["Host"] ?? "host.ru");  
    }  
}

这 就像它会以与假设不同的方式工作...

更新

    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";
    }

工作正常(从配置中,覆盖),但 Reflector 显示相同的:

public static string Host
{
    get
    {
        if (Program.host == null)
        {
            string host = Program.host;
        }
        return (Program.host = GetVal() ?? "default");
    }
}

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");
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

蓦然回首 2024-10-11 12:28:46

这看起来像是 Reflector 的 C# 反汇编中的错误。

从这段代码开始:

public static string _test;
public static string _setting;

public static string Test_1
{
    get { return _test ?? (_setting ?? "default"); }
}

Reflector 显示了这个 C# 反汇编:

public static string Test_1
{
    get
    {
        return (_test ?? (_setting ?? "default"));
    }
}

以及相应的 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 
}

我不是 IL 专家,但这是我的看法:

  • L_0000:ldsfld 推送 _test到评估堆栈
  • L_0005:dup 复制计算堆栈最顶层的值 (_test) 并将其推入堆栈。
  • L_0006:brtrue.sdup 创建的值从堆栈中弹出,如果不为 null 则跳转到 L_0017 >。
  • L_0008:pop 此时,_testnull,因此将该值从堆栈中弹出。

它继续以类似的方式评估 _setting,如果 _setting 也是 null,则最终返回 "default"

现在,如果我们在代码中添加一个赋值,如下所示:

public static string Test_2
{
    get { return _test ?? (_test = (_setting ?? "default")); }
}

Reflector 显示此 C# 反汇编:

public static string Test_2
{
    get
    {
        if (_test == null)
        {
            string text1 = _test;
        }
        return (_test = _setting ?? "default");
    }
}

这是不正确的(如果 _test 不是 null,而不是返回 _test,它将 _setting"default" 分配给 _test,然后返回)。

但是,IL 反汇编看起来像 Test_1 的 IL,在 L_0017L_0018 处有一些额外的指令来完成分配。

.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 
}

最后,如果您复制 Reflector 的 C# 反汇编并针对原始版本运行它,您将看到它产生不同的结果。

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");
            }
        }
    }
}

输出

Test
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 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
安稳善良 2024-10-11 12:28:46

我想我不明白 - 两个代码示例是同义词。

请记住,Reflector 无法从编译器生成的 IL 中重现您的确切语法。有时语法会有所不同,但代码的语义和含义始终是相同的。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文