在variables声明中使用“var”types
我们的内部审计build议我们使用显式variablestypes声明,而不是使用关键字var
。 他们认为使用var
“在某些情况下可能会导致意想不到的结果”。
我没有意识到显式types声明和代码编译为MSIL之后使用var
之间的区别。
审计师是一个受人尊敬的专业人士,所以我不能简单地拒绝这样的build议。
这个怎么样…
double GetTheNumber() { // get the important number from somewhere }
然后在其他地方…
var theNumber = GetTheNumber(); DoSomethingImportant(theNumber / 5);
然后,在将来某个时候,有人注意到GetTheNumber
只能返回整数,所以重构它返回int
而不是double
。
砰! 没有编译器错误,你开始看到意想不到的结果,因为以前的浮点运算现在已经变成了整数运算,没有人注意到。
话虽如此,这样的事情应该通过你的unit testing等,但它仍然是一个潜在的问题。
我倾向于遵循这个计划:
var myObject = new MyObject(); // OK as the type is clear var myObject = otherObject.SomeMethod(); // Bad as the return type is not clear
如果SomeMethod
的返回types改变了,那么这个代码仍然会被编译。 在最好的情况下,你会得到进一步的编译错误,但在最坏的情况下(取决于myObject
的使用方式),你可能不会。 在这种情况下你可能得到的是运行时错误,这可能是很难追查的。
有些情况可能会导致意想不到的结果。 我自己是一个var
迷,但是这可能会出错:
var myDouble = 2; var myHalf = 1 / myDouble;
显然这是一个错误,而不是一个“意外的结果”。 但这是一个难题…
var不是一个dynamictypes,它只是语法糖 。 唯一的例外是匿名types。 来自Microsoft Docs
在许多情况下,var的使用是可选的,仅仅是一个句法的方便。 但是,当variables用匿名types初始化时,如果需要稍后访问对象的属性,则必须将该variables声明为var。
一旦编译成IL ,没有什么区别, 除非你已经明确地定义了与所暗示的types不同的types(虽然我想不出为什么会这么做) 。 编译器不会让你在任何时候改变用var声明的variables的types。
从Microsoft文档 (再次)
一个隐式types的局部variables是强types的,就像你自己声明了types一样,但是编译器决定了types
在某些情况下,var可能会阻止可读性。 更多微软文档陈述:
var的使用至less有可能使你的代码更难以被其他开发者理解。 出于这个原因,C#文档通常只在需要时使用var。
在非generics的世界中,当使用var
而不是types时,如果发生隐式转换,例如在foreach
循环中,你可能会得到不同的行为。
在下面的示例中,发生了从object
到XmlNode
的隐式转换(非genericsIEnumerator
接口仅返回object
)。 如果只是用var
关键字replace循环variables的显式声明,则不再发生这种隐式转换:
using System; using System.Xml; class Program { static void Foo(object o) { Console.WriteLine("object overload"); } static void Foo(XmlNode node) { Console.WriteLine("XmlNode overload"); } static void Main(string[] args) { XmlDocument doc = new XmlDocument(); doc.LoadXml("<root><child/></root>"); foreach (XmlNode node in doc.DocumentElement.ChildNodes) { Foo(node); } foreach (var node in doc.DocumentElement.ChildNodes) { // oops! node is now of type object! Foo(node); } } }
结果是这个代码实际上产生了不同的输出,这取决于你使用了var
还是显式types。 在var
, Foo(object)
重载将被执行,否则Foo(XmlNode)
重载将会被重载。 上述程序的输出是:
XmlNode重载 对象过载
请注意,此行为完全根据C#语言规范。 唯一的问题是, var
推断出一个不同的types( object
),而不是你所期望的 ,而且这个推断在代码中是不明显的。
我没有添加IL来保持简短。 但是如果你愿意,你可以用ildasm来看看编译器实际上为两个foreach循环产生不同的IL指令。
这是一个奇怪的说法,使用var
不应该被使用,因为它“可能会导致在某些情况下意外的结果”,因为在C#语言中有微妙远远比使用var
更复杂。
其中之一是匿名方法的实现细节,可能会导致R#警告“访问修改后的closures”和行为,这是非常不符合您期望的代码。 与var
可以用几句话来解释var
不同,这个行为需要三个长博客文章,其中包括反汇编器的输出以充分解释:
- 在C#中实现匿名方法及其后果(第1部分)
- C#中匿名方法的实现及其后果(第2部分)
- C#中匿名方法的实现及其后果(第三部分)
这是否意味着你也不应该使用匿名方法(即委托,lambdaexpression式)和依赖它们的库,如Linq或者ParallelFX,因为在某些奇怪的情况下,行为可能不是你所期望的?
当然不是。
这意味着你需要了解你正在写的语言,知道它的局限性和边缘情况,并testing事情如你所期望的那样工作。 排除语言特征的基础是“在某些情况下可能会导致意想不到的结果”,这意味着您只剩下很less的语言特征可供使用。
如果他们真的想争论这个问题,可以让他们certificate一些错误可以直接归因于var
使用,而这种明确的types声明会阻止他们。 我怀疑你很快就会收到他们的回信。
他们认为,在某些情况下使用var“可能会导致意想不到的结果”。
如果出乎意料的是,“我不知道如何读取代码并找出它在做什么”,那么是的,这可能会导致意想不到的结果。 编译器必须知道什么types的variables基于写在variables周围的代码。
var关键字是一个编译时function。 编译器会为这个声明提供适当的types。 这就是为什么你不能做这样的事情:
var my_variable = null or var my_variable;
var关键字很好,因为你必须在代码本身中定义更less的信息。 编译器会计算出它应该为你做什么。 这几乎就像在使用接口时总是编程(接口方法和属性由var定义的variables的声明空间中使用的内容定义的接口方法和属性)。 如果variables的types需要改变(当然是合理的),你不需要担心改变variables的声明,编译器会为你处理。 这可能听起来像一件小事,但是如果你必须改变函数的返回值,那么会发生什么情况,并且在整个程序中都会使用这个函数。 如果你没有使用var,那么你必须find并replace每个variables被调用的地方。 使用var关键字,您不必担心这一点。
在提出指导意见的时候,作为一名审计员必须要做的事情,最好的办法就是在安全的方面犯错误,那就是白名单上的好做法/黑名单上的坏做法,而不是告诉人们只是明智的 做对了基于对现状的评估 。
如果你只是说“不要在代码中的任何地方使用var
”,你就会摆脱编码指南中的许多含糊之处。 这应该使代码看起来感觉更加标准化,而不必解决什么时候该做什么以及什么时候这样做的问题。
我个人喜欢var
。 我使用它的所有局部variables。 每时每刻。 如果结果types不清晰,那么var
不是问题,但是用于初始化variables的(命名)方法有问题…
当涉及到使用var关键字时,我遵循一个简单的原则。 如果事先知道types,请不要使用var。 在大多数情况下,我使用var与linq,因为我可能想要返回一个匿名types。
var最好使用当你有明显的声明
ArrayList<Entity> en = new ArrayList<Enity>()
可读性复杂化
var en = new ArrayList<Entity>()
懒惰,清晰的代码,我喜欢它
我只使用var
来清楚variables是什么types,或者根本不需要知道types(例如,GetPerson()应该返回Person
, Person_Class
等等)。
我不使用var
原始types,枚举和string。 我也不使用它的值types,因为值types将被赋值复制,所以variables的types应明确声明。
关于你的审计人员的意见,我想说我们每天都在做的事情中增加更多的代码也会“在某些情况下导致意想不到的结果” 。 这个论证的有效性已经被我们创build的这些错误所证实,所以我会build议永远冻结代码库来防止这种错误。
如果你知道types是什么,使用var是懒惰的代码。 它只是更容易,更干净的阅读。 当看很多很多的代码时,更简单和更干净的总是更好
使用var
和明确指定的variables声明在IL输出中绝对没有区别(可以使用reflection器来certificate这一点)。 我通常只使用var
长嵌套genericstypes, foreach
循环和匿名types,因为我喜欢明确指定的一切。 其他人可能有不同的喜好。
var只是使用显式types声明的简写符号。
你只能在某些情况下使用var; 使用var时,必须在声明时初始化variables。 您不能将其他types的variables分配给variables。
在我看来,很多人都倾向于将“var”关键字与VB6中的“Variant”数据types混淆。
我所看到的使用显式variables声明的“唯一”好处是,select好的types名称可以更清楚地说明你的代码片段的意图(这比任何其他的更重要)。 var关键字的好处真的是Pieter说的。
我也认为如果你宣布你的双打没有D,你会遇到麻烦。 当你编译发行版本时,你的编译器可能会去掉double,并使它们成为一个浮点数来节省空间,因为它不会考虑你的精度。
var会编译成与可以指定的静态types相同的东西。 它只是在您的代码中删除需要明确的types。 它不是一个dynamictypes,不能在运行时更改。 我觉得在foreach循环中使用它非常有用。
foreach(var item in items) { item.name = ______; }
使用枚举时,某些特定types的查找耗时是未知的。 使用var而不是Statictypes会得到相同的结果。 我也发现var的使用使它更易于重构。 当使用不同types的枚举时,foreach将不需要更新。
使用var可能会隐藏逻辑编程错误,否则你会从编译器或IDE获得警告。 看到这个例子:
float distX = innerDiagramRect.Size.Width / (numObjInWidth + 1);
在这里,计算中的所有types都是int
,并且您会得到一个有关可能的分数损失的警告,因为您在float
variables中提取结果。
使用var:
var distX = innerDiagramRect.Size.Width / (numObjInWidth + 1);
这里你不会得到任何警告,因为distX
的types被编译为int
。 如果打算使用浮点值,则这是一个逻辑错误,对您而言是隐藏的,在执行时很难发现,除非在初始计算的结果<1时在稍后的计算中触发divide by zero
exception。