C# 中的 var 关键字会导致装箱吗?

Posted

技术标签:

【中文标题】C# 中的 var 关键字会导致装箱吗?【英文标题】:Does var keyword in C# cause boxing? 【发布时间】:2011-04-03 02:34:55 【问题描述】:

我的老板禁止我使用var,因为这会导致卡顿并降低应用程序的速度。

这是真的吗?

【问题讨论】:

与编程无关。在其他地方讨论运动! 哇。像你这样的人有正当理由在你老板的咖啡里撒尿:) @Akash Kava:请不要在 cmets 中发生激烈的争吵 我用二进制机器码编写所有软件。其他任何事情都是懒惰的。 @Akash Kava:表达你对为什么不应该使用 vars 的看法 一个激战的开端。这是主观的和争论的。就像表达你认为 Java 比 .NET 更好或者为什么不应该使用动态类型一样。如果您想讨论,请将其放在您的博客中。 【参考方案1】:

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

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 给你 :)

【讨论】:

哈,或者将鼠标悬停在x(在 Visual Studio 中)并告诉老板它说“int”怎么样? 我建议在“当着你的面”呈现这个时要保持一些敏感度!给你老板的证据——他可能真的把这个问题和其他事情混为一谈了,证明他的错误不会赢得他的青睐。 我建议更多“当着你的面!”——对一般老板的态度——他们越习惯,我们其他人就越容易接受。而且更有趣。 是的,在你告诉老板他/她错了之后,让我知道你的解雇情况如何。 好吧,我说我的老板今天 var 不是拳击他错了!他说:“真的啊,好吧”。然后他看到了我的代码:foreach(var item in ItemsList) 告诉我现在用正确的类型(如字符串)替换 var。 :P【参考方案2】:

根据 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      

【讨论】:

等等...我看到的区别是什么?哦,没关系,那只是我显示器上的一个地方:)【参考方案3】:

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

您的老板需要阅读文档。 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 必须使用它作为变量的类型。不可能是别的。

【讨论】:

好吧,理论上,类型推断器可以干涉文字的装箱类型。但我想它不会。 ++ 用于咆哮。 即使是这样,老板也犯了微优化的罪,即使这不是真的,他也犯了偏爱性能而不是维护的罪,这几乎是普遍错误的。为什么负责人总是不知道自己在说什么? @annakata:这就是所谓的“彼得原理”:en.wikipedia.org/wiki/Peter_Principle 比彼得原理更简单的是递归证明:如果你的老板比你笨,为什么要雇用他?好吧,他的老板比他还傻。以此类推。 免责声明。我不是指我现在的雇主! (如果我有相反的问题,我被一个聪明的老板诅咒了)。【参考方案4】:

这根本不是真的。

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

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

【讨论】:

"亲爱的编译器..." 很好 :) 但是我认为它的意思是“亲爱的编译器,我不在乎类型是什么,你来决定” @annakata:“你决定”也可以,只要“给我惊喜!”是不可能的:) @delnan 很公平,在某些情况下可能并不明显,但我不想将var 的含义定义为“给我惊喜!” @annakata:不,这并不意味着“我不在乎类型是什么”——而是意味着“我希望类型是右侧表达式的类型="。不涉及猜测。编译器不能随意选择类型。规范非常清楚。 @Jon:也许我说得太轻了。我并不是在暗示编译器猜测或这里有任何不确定性,我试图表达 var 允许我不关心 LHS 的类型,除了 RHS 给我的东西,随便那可能是。价值在于与我脱钩。【参考方案5】:

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

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

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

编辑:不过,正如下面的 cmets 所述,告诉你不要因为错误的原因使用 var 可能不是他的工作......

【讨论】:

+1 用于解释老板的行为。听起来很有可能。但如果老板认为他的知识在他不跟上的情况下仍然有效,那么他至少有点愚蠢。如果他花时间告诉程序员应该使用哪些关键字(尽管他们比他更清楚),那么他也没有做好自己的工作。 我不知道。我认为期望你的老板接受贵公司使用的平台的教育是合理的,特别是如果他决定你如何使用这些平台。 @Brian - 没错。如果你要指定关键词,你最好知道你在说什么。如果您不想跟上最新的技术发展,那很好,但不要事无巨细地规定可以接受哪些关键字。 我必须补充一点:var 非常有用......使用 var 快速编码是好的,但我相信有两个主要途径可以使用或不使用 var。如果您的陈述的右侧很简单,那么 var 就像 var = new List();如果右侧有点太复杂而无法猜测 var IS 则不要.... 只需说明您所期待的内容,以便维护变得更容易。【参考方案6】:

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

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

【讨论】:

以上是关于C# 中的 var 关键字会导致装箱吗?的主要内容,如果未能解决你的问题,请参考以下文章

对值类型调用方法会导致 .NET 中的装箱吗?

c#中的var关键字和object关键字的区别,顺便介绍下object的使用方法以及好处。

Java中的var关键字

C#中的“var”是啥意思? [复制]

C#中隐式类型本地变量var

C#中的var理解