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 在一些非常特殊的情况下也可以避免装箱。
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 关键字会导致装箱吗?的主要内容,如果未能解决你的问题,请参考以下文章