我的老板禁止我使用var
,因为它会导致装箱并减慢应用程序的速度。
真的吗?
一种可行的方法是编写这两种方法:
public static void WithInt()
{
int x = 5;
Console.WriteLine(x);
}
public static void WithVar()
{
var x = 5;
Console.WriteLine(x);
}
编译,并用于ildasm
检查生成的 CIL。显示你的老板。
编辑 @ck 已经为您完成了除了最后一步之外的所有工作:)
继 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
为什么这么多人被老板骂傻?革命啊兄弟们!
你的老板需要阅读文档。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
必须使用它作为变量的类型。不可能是别的。
这根本不是真的。
var
只是意味着“亲爱的编译器,我知道类型是什么,你也知道,所以让我们继续前进吧。”
它使代码更短,并且有些人觉得它更易读(其他人觉得它的可读性较差),但没有任何性能损失。
也许你的老板是一个习惯于这种VARIANT
类型的旧 Visual Basic(如 <= 6.0)程序员。如果您没有在DIM
语句中明确指定变量的类型,那么如果我没记错VARIANT
的话,这是一种union
。在将此类变量传递给函数时,您可以将其视为一种“装箱”和“拆箱”。
有时人们会感到困惑。向你的老板询问他的 Visual Basic 战争故事。倾听、学习并同时赢得一些同情!当您离开办公室时,您可以指出 c# 编译器在编译时会计算出这些东西,并且“装箱”不再是问题。
不要指望你的老板必须跟上语言/API 的最新变化。这不是愚蠢。这是关于有其他事情要做。比如他的工作。
编辑:正如下面评论中所指出的那样,告诉你不要var
出于错误的原因使用可能不是他的工作......
实际上, 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,因此可以优化调用来处理这个问题。
虽然我知道这是一个非常有限的情况,但它可能是一个重要的情况,尤其是在不进行完全增量垃圾收集并暂停线程进行标记/扫描的设备上。