38

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

真的吗?

4

6 回答 6

52

一种可行的方法是编写这两种方法:

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

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

编译,并用于ildasm检查生成的 CIL。显示你的老板。

编辑 @ck 已经为您完成了除了最后一步之外的所有工作:)

于 2010-08-24T10:54:15.107 回答
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      
于 2010-08-24T12:37:04.117 回答
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必须使用它作为变量的类型。不可能是别的。

于 2010-08-24T10:49:48.047 回答
32

这根本不是真的。

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

它使代码更短,并且有些人觉得它更易读(其他人觉得它的可读性较差),但没有任何性能损失。

于 2010-08-24T10:52:40.927 回答
10

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

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

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

编辑:正如下面评论中所指出的那样,告诉你不要var出于错误的原因使用可能不是他的工作......

于 2010-08-24T13:02:12.677 回答
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 内部的值类型(结构),并且可以更有效地使用它 - 即使 List.GetEnumerator 的合同返回一个 IEnumerator。这将删除该结构上的装箱操作并产生更高效的代码。

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

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,因此可以优化调用来处理这个问题。

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

于 2013-08-13T13:35:19.137 回答