【问题标题】:Use of "var" type in variable declaration在变量声明中使用“var”类型
【发布时间】:2010-09-07 11:55:36
【问题描述】:

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

一旦代码编译为 MSIL,我不知道显式类型声明和使用 var 之间有什么区别。

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

【问题讨论】:

标签: c# variable-declaration


【解决方案1】:

我倾向于遵循这个方案:

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

var myObject = otherObject.SomeMethod(); // Bad as the return type is not clear

如果SomeMethod 的返回类型发生变化,那么这段代码仍然可以编译。在最好的情况下,你会得到进一步的编译错误,但在最坏的情况下(取决于myObject 的使用方式)你可能不会。在这种情况下,您可能会遇到运行时错误,这些错误可能很难追踪。

【讨论】:

  • 我们得到了智能感知来向我们展示返回类型是什么。并且方法本身应该清楚地显示它返回的内容。
  • @Claus - 您不必总是将鼠标悬停在 var 上以查看它是什么类型,并且方法名称并不总是符合理想标准。
  • @Claus: 但是如果你只看声明就知道会更容易,而不是必须主动获取它
  • @Oren 你应该知道你在哪个类,方法中的上下文。一旦你知道它,它只是在浪费时间告诉编译器苹果是苹果。更糟糕的是——它已经知道了。
  • Arnis L:这太自大了。每个人都有他们想重写但由于时间限制而无法重写的遗留代码。而且您不应该为其他任何人在以后修复同一文件中的错误而设置代码。
【解决方案2】:

这个怎么样...

double GetTheNumber()
{
    // get the important number from somewhere
}

然后在其他地方......

var theNumber = GetTheNumber();
DoSomethingImportant(theNumber / 5);

然后,在未来的某个时刻,有人注意到 GetTheNumber 只返回整数,因此将其重构为返回 int 而不是 double

砰!没有编译器错误,您开始看到意想不到的结果,因为以前的浮点运算现在变成了整数运算,而没有人注意到。

话虽如此,这种事情应该被你的单元测试等捕获,但它仍然是一个潜在的陷阱。

【讨论】:

  • 如果它只返回整数,你到底能写什么代码来返回意想不到的结果?
  • @badbod99:发挥你的想象力!简单示例:考虑 Console.WriteLine("97 divided by 5 is " + (97 / 5));Console.WriteLine("97 divided by 5 is " + ((double)97 / 5));
  • @badbod99:如果你的薪水不经意间被四舍五入,因为一些浮点运算在没有人注意到的情况下变成了整数运算,你会高兴吗?
  • 事情是 - 这不是 var 关键字的错。这是curvedHands.dll的错。
  • @LukeH - 然而有趣的是,在这个例子中,你很高兴地依赖编译器来推断写入值 5(int 声明)应该被解释为 5.0(@987654331 @ 价值)。此外,鉴于在“真实”生产代码中,常量永远不会像这样被内联声明,而是作为double 类型的实际常量,因为可能常量的含义很重要,那么代码的实际语义将是不变。此外,ReSharper 等工具会警告“可能丢失分数”。我认为这在编写良好的实际代码中不是问题。
【解决方案3】:

var 不是动态类型,它只是syntactic sugar。唯一的例外是匿名类型。 From the Microsoft Docs

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

一旦编译成 IL 就没有区别了除非你明确定义类型与隐含的类型不同(虽然我想不出你为什么会这样)。编译器不会让您在任何时候更改使用 var 声明的变量的类型。

来自Microsoft documentation(再次)

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

在某些情况下 var 会影响可读性。更多Microsoft docs州:

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

【讨论】:

  • @0xA3 你能补充更多细节吗?
  • @0xA3 - 是的,请详细说明。
  • 在 IL 中与显式声明使用 var 推断的相同类型没有区别。如果您实际上想要一种 不同于推断的类型,那么是的,存在差异。
  • 0xA3 - 如果您指的是蒂姆指出的内容,那是一个便宜的镜头,因为这显然与这个问题无关。
  • 有时我认为应该有类似 anon 关键字的匿名类型,而不是 var
【解决方案4】:

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

var myDouble = 2;
var myHalf = 1 / myDouble;

显然这是一个错误,而不是“意外结果”。但它一个陷阱......

【讨论】:

    【解决方案5】:

    在非泛型世界中,当使用 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);
            }
        }
    }
    

    结果是这段代码实际上会产生不同的输出,具体取决于您使用的是var 还是显式类型。使用var,将执行Foo(object) 重载,否则将执行Foo(XmlNode) 重载。因此,上述程序的输出是:

    XmlNode 重载 对象重载

    请注意,此行为完全符合 C# 语言规范。唯一的问题是 var 推断出的类型 (object) 与您预期的不同,而且从查看代码来看,这种推断并不明显。

    我没有添加 IL 来保持简短。但是,如果您愿意,可以使用 ildasm 看看编译器实际上为两个 foreach 循环生成了不同的 IL 指令。

    【讨论】:

    • 无论如何我更喜欢System.Xml.Linq
    • @ChaosPandion:您可能并不总是选择使用 Linq,例如如果您的界面需要XmlDocument。另外XmlDocument 只是一个示例,周围仍然有很多非通用代码(就像几乎所有 Windows 窗体一样),您会遇到我的回答中描述的问题。
    • @Adkins:如果您要枚举 IEnumerator,则该确定类型是 object。我也遇到过这个问题。另一种解决方案是添加.Cast&lt;YourType&gt;(),但只指定类型似乎更直接。
    • @Adkins:这称为方法重载,是 C# 的基本特性之一。查看基类库,发现它是一个被大量使用的核心特性。它可以防止您拥有大量仅因类型而异的不同方法名称。参见例如许多不同的Convert.ToString() 重载。
    • 当然,这里有趣的是var 声明可以满足您的期望,而显式类型声明却没有。使用var 时,您必须手动转换,而使用显式类型声明时,编译器会为您插入转换。当您弄错代码并获得InvalidCastException 时,使用var 的代码将更容易调试以找出它这样做的原因,因为您编写了演员表,而使用显式类型声明您将拥有必须知道编译器为您插入了它。
    【解决方案6】:

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

    其中之一是匿名方法的实现细节,它可能导致 R# 警告“访问修改后的闭包”和行为,这与您在查看代码时所期望的非常不同。与可以用几句话解释的var 不同,这种行为需要三篇长篇博文才能完整解释,其中包括反汇编程序的输出:

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

    当然不是。

    这意味着您需要了解您正在编写的语言,了解其局限性和极端情况,并测试事情是否按您的预期工作。以“在某些情况下可能导致意外结果”为由排除语言功能意味着您可以使用的语言功能非常少。

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

    【讨论】:

    • +1 表示“如果他们真的想争论,请他们证明你的一些错误可以直接归因于使用 var 并且显式类型声明会阻止它们。我怀疑你很快就会收到他们的回音。”
    【解决方案7】:

    他们认为使用 var "可能会导致 在某些情况下出现意外结果。在某些情况下出现意外结果。

    如果意想不到的是,“我不知道如何阅读代码并弄清楚它在做什么”,那么是的,它可能会导致意想不到的结果。编译器必须根据围绕变量编写的代码知道该变量的类型。

    var 关键字是一个编译时特性。编译器将为声明放入适当的类型。这就是为什么你不能做这样的事情:

    var my_variable = null
    or
    var my_variable;
    

    var 关键字很棒,因为您必须在代码本身中定义更少的信息。编译器会弄清楚它应该为你做什么。几乎就像总是在使用接口时对接口进行编程(接口方法和属性由您在 var 定义的变量的声明空间中使用的内容定义)。如果变量的类型需要更改(当然是在合理范围内),您无需担心更改变量声明,编译器会为您处理。这听起来可能是一件微不足道的事情,但是如果您必须更改函数中的返回值,并且该函数在整个程序中都被使用,会发生什么情况。如果您没有使用 var,那么您必须找到并替换每个调用该变量的位置。使用 var 关键字,您无需担心这一点。

    【讨论】:

      【解决方案8】:

      在制定指导方针时,正如审核员必须做的那样,最好选择傻瓜安全,即白名单良好做法/黑名单不良做法,而不是告诉人们保持理智根据对当前情况的评估做正确的事

      如果您只是说“不要在代码中的任何地方使用var”,那么您就可以消除编码指南中的很多歧义。这应该使代码的外观和感觉更加标准化,而无需解决何时执行此操作以及何时执行此操作的问题。

      我个人喜欢var。我将它用于所有局部变量。每时每刻。如果结果类型不清楚,那么这不是var 的问题,而是用于初始化变量的(命名)方法的问题......

      【讨论】:

      • 我不得不说一个好的折衷方案是使用 var 来处理类似这样的事情:IDictionary&lt;string, MyVeryLongGenericClassName&lt;int, string, DateTime&gt;&gt;
      【解决方案9】:

      在使用 var 关键字时,我遵循一个简单的原则。如果您事先知道类型,请不要使用 var。 在大多数情况下,我将 var 与 linq 一起使用,因为我可能希望返回匿名类型。

      【讨论】:

      • 但是使用 var 关键字“无处不在”真的很糟糕吗?
      • 这是个人喜好问题。将var 用于everything 可能很难在不到处使用智能感知的情况下弄清楚发生了什么。
      【解决方案10】:

      当你有明显声明时最好使用var

      ArrayList<Entity> en = new ArrayList<Enity>()
      

      使可读性复杂化

      var en = new ArrayList<Entity>()
      

      懒惰,清晰的代码,我喜欢它

      【讨论】:

        【解决方案11】:

        我只使用var 仅在清楚变量是什么类型或根本不需要知道类型的情况下使用(例如 GetPerson() 应该返回 PersonPerson_Class 等)。

        我不将var 用于原始类型、枚举和字符串。我也不将它用于值类型,因为值类型将通过赋值复制,因此应显式声明变量的类型。

        关于您的审核员 cmets,我想说像我们每天所做的那样添加更多代码行也“在某些情况下会导致意外结果”这个论点我们创建的那些错误已经证明了有效性,因此我建议永远冻结代码库以防止这种情况发生。

        【讨论】:

          【解决方案12】:

          如果您知道类型将是什么,则使用 var 是惰性代码。它只是更容易和更清洁的阅读。在查看大量代码时,更简单更简洁总是更好

          【讨论】:

          • 我不同意这一点。我不认为你通过明确给出类型来节省那么多。在大多数情况下,您将查看变量 anywho 中的内容,而不仅仅是查看名称和类型。
          • 我说的是一目了然,眼睛更容易阅读,虽然这可能只是个人喜好
          • 整个问题(恕我直言)归结为个人喜好。 Auditor 显然不是 var 的粉丝,所以他建议大家避免使用它。如果明天另一位审计员从 OP 通过公司,可能会完全相反。这种微不足道的事情只不过是偏好。
          • @Adkins 这是真的,业务逻辑应该落实到位,以便每个人都遵循相同的准则。
          【解决方案13】:

          对于使用var 的变量声明和明确指定的变量声明(您可以使用反射器证明这一点),IL 输出绝对没有区别。我通常只对长嵌套的泛型类型、foreach 循环和匿名类型使用 var,因为我喜欢明确指定所有内容。其他人可能有不同的偏好。

          【讨论】:

          • 不,请参阅我的答案以获取不是这种情况的示例。
          • 我会说 is 是这种情况,您的示例只是 C# 1 功能(隐式 foreach 转换)以意想不到的方式与 C# 3 交互的人工制品var - 如果元素类型是 object,你会得到与 var 完全相同的 IL
          • 在您的示例中,存在差异,因为推断的类型不是您真正想要的类型。但是,如果您将 node 明确声明为 object 类型(即,如果推断的类型 您想要的类型),则没有区别。
          • @thecoop:隐式 foreach-casting 仍然发生在 C# 4.0 中,不同的是当您使用 foreachIEnumerable&lt;T&gt; 时,枚举数是强类型的。
          【解决方案14】:

          var 只是使用显式类型声明的简写符号。

          您只能在某些情况下使用 var;使用 var 时,您必须在声明时初始化变量。 之后不能将其他类型的变量分配给该变量。

          在我看来,很多人倾向于将 'var' 关键字与 VB6 中的 'Variant' 数据类型混淆。

          【讨论】:

            【解决方案15】:

            我看到的使用显式变量声明的“唯一”好处是,通过精心选择的类型名,您可以更清楚地说明代码的意图(这比 imo 中的其他任何事情都更重要)。 var 关键字的好处确实是 Pieter 所说的。

            【讨论】:

              【解决方案16】:

              我还认为,如果你在最后没有 D 的情况下宣布你的双打,你会遇到麻烦。当您编译发布版本时,您的编译器可能会剥离双精度并使它们成为浮点数以节省空间,因为它不会考虑您的精度。

              【讨论】:

                【解决方案17】:

                var 将编译为与可以指定的静态类型相同的内容。它只是消除了在代码中明确使用该类型的需要。它不是动态类型,在运行时不会/不能更改。我发现在 foreach 循环中使用它非常有用。

                foreach(var item in items)
                {
                item.name = ______;
                }
                

                使用枚举时,有时不知道特定类型的查找耗时。使用 var 而不是静态类型将产生相同的结果。 我还发现使用 var 使其更易于重构。当使用不同类型的枚举时,不需要更新 foreach。

                【讨论】:

                  【解决方案18】:

                  使用 var 可能会隐藏逻辑编程错误,否则您会收到来自编译器或 IDE 的警告。看这个例子:

                  float distX = innerDiagramRect.Size.Width / (numObjInWidth + 1);
                  

                  这里,计算中的所有类型都是int,并且您会收到关于可能丢失分数的警告,因为您在float 变量中获取了结果。

                  使用 var:

                  var distX = innerDiagramRect.Size.Width / (numObjInWidth + 1);
                  

                  这里没有警告,因为distX 的类型被编译为int。如果您打算使用浮点值,这是一个对您隐藏的逻辑错误,并且在执行时很难发现,除非它在以后的计算中触发 divide by zero 异常(如果此初始计算的结果为

                  【讨论】:

                    猜你喜欢
                    • 2011-12-12
                    • 1970-01-01
                    • 2010-10-24
                    • 1970-01-01
                    • 2010-09-28
                    • 2020-02-12
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多