C#中的var关键字是否导致拳击?

时间:2010-08-24 10:48:57

标签: c# performance var boxing

我的老板禁止我使用var,因为它会导致拳击并减慢应用程序的速度。

这是真的吗?

6 个答案:

答案 0 :(得分:52)

可能有效的方法是编写这两种方法:

public static void WithInt()
{
    int x = 5;
    Console.WriteLine(x);
}

public static void WithVar()
{
    var x = 5;
    Console.WriteLine(x);
}

编译,并使用ildasm检查生成的CIL。告诉你的老板。

编辑 @ck has done all but the last step给你:)

答案 1 :(得分:37)

根据Aakash的回答,这是IL :(谢谢LINQPad

WithInt:
IL_0000:  ldc.i4.5    
IL_0001:  stloc.0     
IL_0002:  ldloc.0     
IL_0003:  call        System.Console.WriteLine
IL_0008:  ret         

WithVar:
IL_0000:  ldc.i4.5    
IL_0001:  stloc.0     
IL_0002:  ldloc.0     
IL_0003:  call        System.Console.WriteLine
IL_0008:  ret      

答案 2 :(得分:32)

为什么有这么多人被愚蠢的老板诅咒?革命,兄弟们!

你的老板需要阅读文档。 var使编译器通过查看初始化表达式的静态类型来确定变量类型。无论您是手动指定类型还是使用var并让编译器为您解决它,它在运行时都没有丝毫差异。

更新在问题的评论中,Hans Passant问道

  

你能想到任何var初始化器吗?   这导致拳击而不使用   投?

强制进行此类转换的自包含表达式的示例是:

var boxedInt = new Func<int, object>(n => n)(5);

但这与:

完全相同
object boxedInt = new Func<int, object>(n => n)(5);

换句话说,这与var没有任何关系。我的初始化表达式的结果是object,因此var必须使用它作为变量的类型。它不可能是其他任何东西。

答案 3 :(得分:32)

这根本不是真的。

var只是意味着“亲爱的编译器,我知道类型是什么,所以你也是,所以让我们继续吧。”

它使代码更短,有些人发现它更具可读性(其他人发现它更不易读),但是没有任何性能损失。

答案 4 :(得分:10)

也许你的老板是用于VARIANT类型的旧Visual Basic(在&lt; = 6.0中)编程器。如果您没有在DIM语句中明确指定变量的类型,那么如果我没记错的话,那就是VARIANT union。将这些变量传递给函数时,您可以将其视为一种“装箱”和“拆箱”。

有时人们会感到困惑。向你的老板询问他的Visual Basic战争故事。聆听,学习并同时获得一些同情!当您离开办公室时,您可以指出c#编译器在编译时将这些内容计算出来并且“装箱”不再是问题。

不要指望你的老板必须跟上语言/ API的最新变化。这不是愚蠢的。这是关于还有其他事情要做。比如他的工作。

修改:如下面的评论中所述,告诉您不要错误地使用var可能不是他的工作......

答案 5 :(得分:3)

实际上,var也可以在某些非常特殊的情况下避免装箱。

static void Main(string[] args)
{
    List<Int32> testList = new List<Int32>();
    IEnumerator<Int32> enumAsInterface = testList.GetEnumerator();
    var enumAsStruct = testList.GetEnumerator();
}

以下IL中的结果:

.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 27 (0x1b)
    .maxstack 1
    .entrypoint
    .locals init (
        [0] class [mscorlib]System.Collections.Generic.List`1<int32> testList,
        [1] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> enumAsInterface,
        [2] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> enumAsStruct
    )

    IL_0000: nop
    IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
    IL_0006: stloc.0
    IL_0007: ldloc.0
    IL_0008: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator()
    IL_000d: box valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>
    IL_0012: stloc.1
    IL_0013: ldloc.0
    IL_0014: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator()
    IL_0019: stloc.2
    IL_001a: ret
} // end of method Program::Main

请注意,第二个(var赋值)知道此返回值是List内部的valuetype(struct),并且可以更有效地使用它 - 即使List.GetEnumerator中的契约返回一个IEnumerator。这将删除该结构上的装箱操作,从而产生更高效的代码。

这就是为什么,例如,在下面的代码中,foreach循环和第一个using / while对不会导致垃圾(由于缺少装箱),但第二个使用/ while循环确实(因为它包装了返回struct):

class Program
{
    static void Main(string[] args)
    {
        List<Int32> testList = new List<Int32>();

        foreach (Int32 i in testList)
        {
        }

        using (var enumerator = testList.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
            }
        }

        using (IEnumerator<Int32> enumerator = testList.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
            }
        }
    }
}

另请注意,将此项从“List”更改为“IList”将破坏此优化,因为IList只能推断出IEnumerator类型的接口正在返回。使用List变量,编译器可以更智能,并且可以看到唯一有效的返回值是[mscorlib] System.Collections.Generic.List`1 / Enumerator,因此可以优化调用来处理此问题。

虽然我知道这是一个非常有限的情况,但它可能是一个重要的情况,特别是在没有进行完全增量垃圾收集并暂停线程进行标记/扫描的设备上。