在变量声明中使用"var"类型

Pet*_*lek 37 c# variable-declaration

我们的内部审计建议我们使用显式变量类型声明而不是使用关键字var.他们认为使用var"可能会在某些情况下导致意外结果".

var一旦代码编译成MSIL,我不知道显式类型声明和使用之间的任何区别.

审计员是一位受人尊敬的专业人士,所以我不能简单地拒绝这样的建议.

Luk*_*keH 50

这个怎么样...

double GetTheNumber()
{
    // get the important number from somewhere
}
Run Code Online (Sandbox Code Playgroud)

其他地方......

var theNumber = GetTheNumber();
DoSomethingImportant(theNumber / 5);
Run Code Online (Sandbox Code Playgroud)

然后,在未来的某个时刻,有人注意到GetTheNumber只返回整数,所以重构它int而不是返回double.

砰! 没有编译器错误,你开始看到意外的结果,因为以前的浮点运算现在已成为整数运算而没有任何人注意到.

话虽如此,这种事情应该是你的单元测试等,但它仍然是一个潜在的问题.

  • @LukeH - 有趣的是,在这个例子中,你很高兴依靠编译器来推断写入值5(一个`int`声明)应该被解释为5.0(一个`double`值).另外,鉴于在'真实'生产代码中,常量永远不会像这样声明内联,而是作为`double`类型的实际常量,因为可能是常量的含义很重要,那么代码的实际语义将是不变.此外,像ReSharper这样的工具警告"可能会失去分数".我认为在编写良好的现实代码中这不是问题. (8认同)
  • 如果它只返回整数,你可以编写哪些代码会返回意外结果? (4认同)
  • @ badbod99:用你的想象力!琐碎的例子:考虑`Console.WriteLine("97除以5是"+(97/5));`对比`Console.WriteLine("97除以5是"+((double)97/5)); . (4认同)
  • 事情是 - 这不是`var`关键字的错.这是`curvedHands.dll`的错. (4认同)
  • @ badbod99:如果你的工资被无意中缩小了,你会感到高兴,因为一些浮点运算成为整数运算而没有人注意到吗? (3认同)

Chr*_*isF 23

我倾向于遵循这个方案:

var myObject = new MyObject(); // OK as the type is clear

var myObject = otherObject.SomeMethod(); // Bad as the return type is not clear
Run Code Online (Sandbox Code Playgroud)

如果返回类型SomeMethod永远改变,那么此代码仍将编译.在最好的情况下,您会进一步得到编译错误,但在最坏的情况下(取决于myObject使用方式)您可能不会.在这种情况下你可能得到的是运行时错误,这可能很难追查.

  • 我们得到了intellisense来向我们展示返回类型是什么.方法本身应该明确它返回的内容. (7认同)
  • @Claus - 你并不总是希望将鼠标悬停在`var`上以查看它是什么类型,方法名称并不总是符合理想标准. (3认同)
  • @Claus:但是你可以更容易地看到声明并知道,而不是必须积极地获取它 (2认同)
  • Arnis L:那太傲慢了.每个人都有他们想要重写的遗留代码,但由于时间限制而不能.并且你不应该为以后修复同一文件中的bug的其他人提供陷阱. (2认同)

Dar*_*mas 14

有些情况可能会导致意想不到的结果.我var自己也是粉丝,但这可能会出错:

var myDouble = 2;
var myHalf = 1 / myDouble;
Run Code Online (Sandbox Code Playgroud)

显然这是一个错误而不是"意外结果".但这一个问题......


bad*_*d99 13

var不是动态类型,它只是语法糖.唯一的例外是Anonymous类型. 来自Microsoft Docs

在许多情况下,var的使用是可选的,只是语法上的便利.但是,当使用匿名类型初始化变量时,如果需要稍后访问对象的属性,则必须将变量声明为var.

编译为IL之后没有区别,除非您已明确将类型定义为与隐含的类型不同(尽管我无法想到您为什么会这样).编译器不允许在任何时候更改用var声明的变量的类型.

Microsoft文档(再次)

隐式类型的局部变量是强类型的,就像您自己声明了类型一样,但编译器确定了类型

在某些情况下,var会妨碍可读性.更多Microsoft文档声明:

使用var确实至少有可能使您的代码对其他开发人员更难理解.因此,C#文档通常仅在需要时才使用var.

  • @ 0xA3你可以添加更多细节吗? (6认同)
  • 0xA3 - 如果你指的是Tim指出的话,这是一个便宜的镜头,因为这显然与这个问题无关. (4认同)

Dir*_*mar 8

在非泛型世界var中,每当发生隐式转换时(例如在foreach循环内)使用而不是类型时,您可能会得到不同的行为.

在下面的例子中,从隐式转换objectXmlNode发生(非通用IEnumerator接口只返回object).如果只是用var关键字替换循环变量的显式声明,则不再发生此隐式转换:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

结果是,根据您使用的var还是显式类型,此代码实际上会产生不同的输出.随着varFoo(object)超载将被执行,否则Foo(XmlNode)过载会.因此,上述计划的产出是:

XmlNode overload
object overload

请注意,此行为完全符合C#语言规范.唯一的问题是var推断出一种不同于你期望的类型(object),并且通过查看代码,这种推断并不明显.

我没有添加IL以保持简短.但是如果你想要,你可以看看ildasm,看看编译器实际上为两个foreach循环生成不同的IL指令.

  • 当然,这里有趣的是,它是`var`声明,它可以完成你所期望的,而显式类型声明则没有.当使用`var`时你必须手动转换,而使用显式类型声明时,编译器会为你插入转换.当你得到错误的代码并获得`InvalidCastException`时,使用`var`的代码将更加明显地调试以找出它为什么这样做,因为你编写了强制转换,而使用显式类型声明你会有必须知道编译器为你插入它. (4认同)
  • 无论如何,我更喜欢`System.Xml.Linq`. (3认同)

Gre*_*ech 7

这是一个奇怪的说法,即var永远不应该使用使用,因为它"在某些情况下可能会导致意想不到的结果",因为C#语言中的微妙之处远比使用它复杂得多var.

其中之一是匿名方法的实现细节,这些方法可能导致R#警告"访问修改后的闭包"以及与查看代码时非常不同的行为.不同于几句话var可以解释,这种行为需要三篇很长的博客文章,其中包括反汇编程序的输出,以便完整解释:

这是否意味着您也不应该使用匿名方法(即委托,lambdas)和依赖它们的库(如Linq或ParallelFX),因为在某些奇怪的情况下,行为可能不是您所期望的?

当然不是.

这意味着您需要了解您正在编写的语言,了解其局限性和边缘情况,并测试事情是否按预期工作.排除语言功能的基础是"在某些情况下可能会导致意外结果",这意味着您只能使用很少的语言功能.

如果他们真的想争论折腾,请他们证明你的一些错误可以直接归因于使用,var并且显式类型声明会阻止它们.我怀疑你很快会收到他们的回复.