代码之家  ›  专栏  ›  技术社区  ›  Vinko Vrsalovic

为什么“is”实现为“as”?

  •  28
  • Vinko Vrsalovic  · 技术社区  · 15 年前

    假设这是一个非常自然的用例(如果你不知道 as 事实上)

    if (x is Bar) {
       Bar y = x as Bar;
       something();
    }
    

    有效等效(即编译器生成的 CIL 上述代码将等同于:

    Bar y = x as Bar;
    if (y != null) {
        y = x as Bar; //The conversion is done twice!
        something();
    }
    

    编辑:

    我想我的问题还没有弄清楚。我绝不会写第二个片段,因为它当然是多余的。我声称编译器在编译第一个代码段时生成的CIL相当于第二个代码段,这是多余的。问题:a)这是正确的吗?b)如果是,为什么是 is 像那样执行?

    这是因为我发现第一个片段比实际写得好的片段更清晰、更漂亮。

    Bar y = x as Bar;
    if (y != null) {
       something();
    }
    

    结论:

    优化 / 作为 案例不是编译器的责任,而是JIT的责任。

    此外,与空检查一样,它的指令比两种可选方案都少(而且价格也低)( 作为 cast )

    附录:

    与nullcheck相同的CIL(.net 3.5):

    L_0001: ldarg.1
    L_0002: isinst string
    L_0007: stloc.0
    L_0008: ldloc.0
    L_0009: ldnull
    L_000a: ceq
    L_000c: stloc.1
    L_000d: ldloc.1
    L_000e: brtrue.s L_0019
    L_0011: ldarg.0
    L_0019: ret
    

    IS和CAST的CIL(.NET 3.5):

    L_0001: ldarg.1
    L_0002: isinst string
    L_0007: ldnull
    L_0008: cgt.un
    L_000a: ldc.i4.0
    L_000b: ceq
    L_000d: stloc.1
    L_000e: ldloc.1
    L_000f: brtrue.s L_0021
    L_0012: ldarg.1
    L_0013: castclass string
    L_0018: stloc.0
    L_0019: ldarg.0
    L_0021: ret
    

    IS和AS的CIL(.NET 3.5):

    L_0001: ldarg.1
    L_0002: isinst string
    L_0007: ldnull
    L_0008: cgt.un
    L_000a: ldc.i4.0
    L_000b: ceq
    L_000d: stloc.1
    L_000e: ldloc.1
    L_000f: brtrue.s L_0021
    L_0012: ldarg.1
    L_0013: isinst string
    L_0018: stloc.0
    L_0019: ldarg.0
    L_0021: ret
    

    这些已经被编辑为shortness(方法声明、nop和对something()的调用被删除)。

    10 回复  |  直到 10 年前
        1
  •  12
  •   Eric Lippert    15 年前

    a)这个正确吗

    是的,尽管我会用另一种方式陈述。您的意思是“is”是一个语法甜头,后面跟着空检查。我会用另一种方式说:“as”是“检查类型实现、如果成功则强制转换、如果失败则为空”的语法甜头。

    也就是说,我更倾向于说

    if (x is Bar) { 
       Bar y = x as Bar; 
       something(); 
    } 
    

    实际上相当于

    if (x is Bar) { 
       Bar y = (x is Bar) ? (Bar)x : (Bar) null; 
       something(); 
    } 
    

    你想用“is”来定义“as”,而不是用另一种方式。问题真的应该是“为什么要像现在这样实施?”:-)

    b)如果是,为什么要这样实施?

    因为那是 规范的正确实施 .

    我想我不是按照你的思路来的。这个实现有什么问题吗?您希望如何实现它?您可以使用“isist”和“castclass”指令;描述您希望看到的程序的代码生成器。

        2
  •  9
  •   Damien_The_Unbeliever    15 年前

    那么,可用的IL指令(isist)将返回适当类型的对象,或者如果不可能进行这种转换,则返回空值。如果无法进行转换,则不会引发异常。

    考虑到这一点,“is”和“as”都很容易实现。在这种情况下,我不会声称“is”实现为“as”,只是基础IL指令允许两者同时发生。现在,为什么编译器不能将“is”和“a s”优化为单个is is t调用,这是另一回事。在这种情况下,它可能与变量作用域有关(即使到了IL的时候,作用域并不真正存在)

    编辑

    再想一想,在不知道讨论中的变量不受其他线程更新的影响的情况下,不能将“is”后跟“as”优化为单个is is t调用。

    假设x是一个字符串:

    //Thread1
    if(x is string)
    
    //Thread2
    x = new ComplexObject();
    
    //Thread1
        y = x as string
    

    这里,y应该为空。

        3
  •  5
  •   Will Vousden    15 年前

    在您的示例中,使用 as 无论如何都是多余的。既然你已经知道了 x is Bar ,您应该使用演员表:

    if (x is Bar)
    {
        Bay y = (Bar)x;
    }
    

    或者,使用转换 作为 检查是否为空:

    Bar y = x as Bar;
    if (y != null)
    {
    
    }
    
        4
  •  5
  •   Steve    15 年前

    首先,我不同意您的前提,即这是更典型的用例。它可能是您最喜欢的方法,但惯用的方法是“as+null check”样式:

    Bar y = x as Bar; 
    if (y != null) { 
       something(); 
    }
    

    正如您发现的那样,“is”方法需要额外的“as”或强制转换,这就是为什么在我的经验中,“as”带空检查是执行此操作的标准方法。

    我认为这种“as”方法没有什么冒犯性,就我个人而言,我认为它在眼睛上没有比任何其他代码更令人不快的了。

    至于你的实际问题,为什么 is 关键字是根据 as 关键字,我不知道,但我确实喜欢你问题中的字谜游戏:)我怀疑两者都没有实际实现在另一个方面,但你用来从IL生成C的工具(我猜是Reflector)解释了IL在 作为 .

        5
  •  2
  •   J. Random Coder    15 年前

    你一秒钟也不做 y = x as Bar; 因为你已经有了Y,也就是酒吧。

        6
  •  1
  •   Ian Ringrose    15 年前

    你现在可以把代码写成

    DoIfOfType<Bar>(possibleBar, b => b.something())
    

    我想说的更清楚一点,但如果没有编译器真正的魔力,速度就不会那么快。

        7
  •  1
  •   Peter Mortensen icecrime    10 年前

    根据博客帖子 How Many Passes? 作者埃里克·利珀特,这是一个编译器的通行证。引述:

    然后我们运行一个优化过程 重写琐碎的“is”和“as” 运算符。

    因此,也许这就是为什么您看到为两个片段生成的相同CIL的原因。

        8
  •  0
  •   Craig Gidney Mihai    15 年前

    如果将声明放在循环中,“y”的范围将减小。

    写这篇文章的人可能更喜欢将“x”转换为“t”而不是“(t)x”,并希望限制“y”的范围。

        9
  •  0
  •   Wyatt Barnett    15 年前

    您忘记了值类型。如:

        static void Main(string[] args)
        {
            ValueType vt;
            FooClass f = vt as FooClass;
    
        }
    
        private class FooClass
        {
            public int Bar { get; set; }
        }
    

    不会编译为无法这样转换值类型。

        10
  •  -1
  •   Peter Mortensen icecrime    10 年前

    我强烈怀疑 比快 作为 不需要分配。因此,如果x很少是条形的,那么第一个片段是好的。如果x主要是bar,那么 作为 建议,因为不需要第二次铸造。这取决于代码的用法和环境。