草庐IT

c# - 奇怪的空合并运算符自定义隐式转换行为

coder 2023-07-06 原文

注意:这似乎已在 Roslyn 中修复

这个问题是在写我对 this one 的回答时出现的,它讨论了 null-coalescing operator 的结合性.

提醒一下,空合并运算符的思想是形式的表达式

x ?? y

首先计算x,然后:

  • 如果 x 的值为 null,则计算 y,这就是表达式的最终结果
  • 如果 x 的值不为空,则 y 求值,并且 x 的值> 是表达式的最终结果,在必要时转换为 y 的编译时类型

现在通常不需要转换,或者它只是从可空类型到不可空类型 - 通常类型是相同的,或者只是从(比如)int ?int。但是,您可以创建自己的隐式转换运算符,并在必要时使用它们。

对于 x 的简单情况 ?? y,我没有看到任何奇怪的行为。但是,使用 (x ?? y) ?? z 我看到一些令人困惑的行为。

这是一个简短但完整的测试程序——结果在评论中:

using System;

public struct A
{
    public static implicit operator B(A input)
    {
        Console.WriteLine("A to B");
        return new B();
    }

    public static implicit operator C(A input)
    {
        Console.WriteLine("A to C");
        return new C();
    }
}

public struct B
{
    public static implicit operator C(B input)
    {
        Console.WriteLine("B to C");
        return new C();
    }
}

public struct C {}

class Test
{
    static void Main()
    {
        A? x = new A();
        B? y = new B();
        C? z = new C();
        C zNotNull = new C();

        Console.WriteLine("First case");
        // This prints
        // A to B
        // A to B
        // B to C
        C? first = (x ?? y) ?? z;

        Console.WriteLine("Second case");
        // This prints
        // A to B
        // B to C
        var tmp = x ?? y;
        C? second = tmp ?? z;

        Console.WriteLine("Third case");
        // This prints
        // A to B
        // B to C
        C? third = (x ?? y) ?? zNotNull;
    }
}

所以我们有三种自定义值类型,ABC,以及从 A 到 B、A 到 C 的转换,以及B 到 C。

我能理解第二种情况和第三种情况...但是为什么第一种情况会有额外的 A 到 B 转换?特别是,我真的期望第一种情况和第二种情况是同一件事——毕竟它只是将表达式提取到局部变量中。

有人知道发生了什么事吗?当涉及到 C# 编译器时,我非常犹豫是否要说“错误”,但我对发生了什么感到困惑...

编辑:好的,这是一个更糟糕的例子,感谢配置器的回答,这让我更有理由认为这是一个错误。编辑:样本现在甚至不需要两个空合并运算符......

using System;

public struct A
{
    public static implicit operator int(A input)
    {
        Console.WriteLine("A to int");
        return 10;
    }
}

class Test
{
    static A? Foo()
    {
        Console.WriteLine("Foo() called");
        return new A();
    }

    static void Main()
    {
        int? y = 10;

        int? result = Foo() ?? y;
    }
}

这个的输出是:

Foo() called
Foo() called
A to int

Foo() 在这里被调用两次这一事实令我非常惊讶 - 我看不出有任何理由让表达式被 求值 两次。 p>

最佳答案

感谢所有为分析此问题做出贡献的人。这显然是一个编译器错误。它似乎仅在合并运算符左侧存在涉及两个可空类型的提升转换时才会发生。

我还没有确定哪里出了问题,但在编译的“可空降低”阶段的某个时刻——在初始分析之后但在代码生成之前——我们减少了表达式

result = Foo() ?? y;

从上面的例子到道德等价物:

A? temp = Foo();
result = temp.HasValue ? 
    new int?(A.op_implicit(Foo().Value)) : 
    y;

显然这是不正确的;正确的降低是

result = temp.HasValue ? 
    new int?(A.op_implicit(temp.Value)) : 
    y;

根据我目前的分析,我最好的猜测是可空优化器在这里偏离了轨道。我们有一个可空优化器,用于查找我们知道可空类型的特定表达式不可能为空的情况。考虑以下朴素的分析:我们可能首先说

result = Foo() ?? y;

相同
A? temp = Foo();
result = temp.HasValue ? 
    (int?) temp : 
    y;

然后我们可能会说

conversionResult = (int?) temp 

相同
A? temp2 = temp;
conversionResult = temp2.HasValue ? 
    new int?(op_Implicit(temp2.Value)) : 
    (int?) null

但是优化器可以介入并说“哇,等一下,我们已经检查过 temp 不为空;没有必要再次检查它是否为空,因为我们正在调用提升转换运算符”。我们让他们将其优化为仅

new int?(op_Implicit(temp2.Value)) 

我的猜测是我们在某个地方缓存了这样一个事实,即 (int?)Foo() 的优化形式是 new int?(op_implicit(Foo().Value)) 但这实际上并不是我们想要的优化形式;我们想要 Foo()-replaced-with-temporary-and-then-converted 的优化形式。

C# 编译器中的许多错误都是错误的缓存决策造成的。明智的一句话:每次缓存一个事实供以后使用时,如果相关的更改发生,您可能会造成不一致。在这种情况下,初始分析后发生变化的相关事情是对 Foo() 的调用应始终实现为对临时文件的提取。

我们对 C# 3.0 中的可空重写过程进行了大量重组。该错误会在 C# 3.0 和 4.0 中重现,但不会在 C# 2.0 中重现,这意味着该错误可能是我的错。对不起!

我会把一个错误输入数据库,我们会看看是否可以为该语言的 future 版本修复这个错误。再次感谢大家的分析;这非常有帮助!

更新:我为 Roslyn 从头开始​​重写了可空优化器;它现在做得更好并且避免了这些奇怪的错误。有关 Roslyn 中优化器如何工作的一些想法,请参阅我的系列文章,这些文章从这里开始:https://ericlippert.com/2012/12/20/nullable-micro-optimizations-part-one/

关于c# - 奇怪的空合并运算符自定义隐式转换行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6256847/

有关c# - 奇怪的空合并运算符自定义隐式转换行为的更多相关文章

  1. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  2. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  3. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  4. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  5. ruby - 触发器 ruby​​ 中 3 点范围运算符和 2 点范围运算符的区别 - 2

    请帮助我理解范围运算符...和..之间的区别,作为Ruby中使用的“触发器”。这是PragmaticProgrammersguidetoRuby中的一个示例:a=(11..20).collect{|i|(i%4==0)..(i%3==0)?i:nil}返回:[nil,12,nil,nil,nil,16,17,18,nil,20]还有:a=(11..20).collect{|i|(i%4==0)...(i%3==0)?i:nil}返回:[nil,12,13,14,15,16,17,18,nil,20] 最佳答案 触发器(又名f/f)是

  6. ruby - 如果指定键的值在数组中相同,如何合并哈希 - 2

    我有一个这样的哈希数组:[{:foo=>2,:date=>Sat,01Sep2014},{:foo2=>2,:date=>Sat,02Sep2014},{:foo3=>3,:date=>Sat,01Sep2014},{:foo4=>4,:date=>Sat,03Sep2014},{:foo5=>5,:date=>Sat,02Sep2014}]如果:date相同,我想合并哈希值。我对上面数组的期望是:[{:foo=>2,:foo3=>3,:date=>Sat,01Sep2014},{:foo2=>2,:foo5=>5:date=>Sat,02Sep2014},{:foo4=>4,:dat

  7. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  8. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

  9. ruby - 如何在 Grape 中定义哈希数组? - 2

    我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>

  10. ruby - 获取模块中定义的所有常量的值 - 2

    我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c

随机推荐