隐式类型如何使代码更清晰?

时间:2010-08-06 10:00:44

标签: c# implicit-typing

在我正在阅读的一本书中,它指出隐式输入使得以下代码比不使用var关键字更清晰:

var words = new[] { "a", "b", null, "d" };

foreach (var item in words)
{
    Console.WriteLine(item);
}

在我看来,情况恰恰相反:如果您使用string代替,那么代码的读者会立即知道它是foreach循环中的字符串,而不是必须在代码中查找变量已定义。

隐式输入如何使上述代码更清晰?

附录

这本书是C # 3.0 - Die Neuerungen. schnell + kompakt,用德语写成,实际文字是:

  

Das Schluesselwort var kann auch beim Durchlaufen von foreach-Schleifen verwendet werden,um somit den Code uebersichtlicher und einfacher zu gestalten。 Besonders bei komplexen Typen kann man auf diese Art und Weise Programmierfehler verhindern。

这是我的翻译:

  

在迭代foreach循环时也可以使用var关键字,从而使代码更容易和更简单地创建。特别是在使用复杂类型时,这可以防止编程错误。

好的,现在更仔细地阅读它,他实际上声明foreach循环中的var使代码更容易创建但不一定更容易阅读。

13 个答案:

答案 0 :(得分:11)

就个人而言,我同意你的看法。我不确定我会使用哪个词更清楚,但在某些情况下,var关键字肯定会使它更清洁,即:

var myClass = new ExtremelyLongClassNameIWouldntWantToTypeTwice();

答案 1 :(得分:11)

我认为奇怪的是,只有C#和Java程序员似乎遭受了一种痛苦,使他们无法从代码上下文中提取信息,而Python,JavaScript,Ruby,F#,Haskell和其他人的开发人员似乎对这个。为什么他们似乎做得很好,但我们C#程序员需要进行这样的讨论?

如果前面的显式类型声明是草率或懒惰,这是否意味着没有高质量,可读的Python代码?事实上,没有多少人称赞Python具有可读性?有许多事情让我对JavaScript中的动态类型感到不满,但缺乏显式类型声明并不是其中之一。

静态类型语言中的类型推断应该是常态,而不是例外;它减少了视觉混乱和冗余,同时当你明确指定一个类型时你的意图更清晰,因为你想要一个较少派生的类型(IList<int> list = new List<int>();)。

有些人可能会反对var这样的案例:

var c = SomeMethod();

那么,我要说你应该给你的变量更明智的名字。

改进:

var customer = SomeMethod();

更好:

var customer = GetCustomer();

让我们尝试显式输入:

Customer customer = GetCustomer();

您现在拥有的以前没有的信息是什么?你现在知道它的类型为Customer,但你已经知道了,对吧?如果您已熟悉代码,则只需通过变量名称知道customer可以预期的方法和属性。如果您还不熟悉代码,则不知道Customer有哪些方法。这里的显式类型没有增加任何价值。

也许var的某些反对者可能会承认,在上面的例子中,var没有任何伤害。但是,如果方法不返回像CustomerOrder之类的简单且众所周知的类型,但是某些已处理的值,如某种字典?类似的东西:

var ordersPerCustomer = GetOrdersPerCustomer();

我不知道返回什么,可能是字典,列表,数组,真的。但这有关系吗?从代码中,我可以推断出我将拥有一个可迭代的客户集合,其中每个Customer依次包含Order的可迭代集合。 我真的不在乎这里的类型。我知道我需要知道什么,如果事实证明我错了,这是误导我的名字的方法的错,无法通过显式类型声明修复。

让我们看一下显式版本:

IEnumerable<IGrouping<Customer,Order>> ordersPerCustomer = GetOrdersPerCustomer();

我不了解你,但我发现从中提取我需要的信息要困难得多。至少是因为包含实际信息(变量名称)的位在右侧,我需要花更长的时间才能找到它,所有那些疯狂的<和{{1}都会在视觉上模糊不清}。实际的类型是没有价值的,它是gobbledygook,特别是因为要理解它,你需要知道这些泛型类型的作用。

如果在任何时候你不确定方法做什么,或者什么变量包含,只要从名称中,你应该给它一个更好的名字。这比查看它的类型更有价值。

显式输入不应该需要,如果是,则代码有问题,而不是类型推断。 不能,因为其他语言显然也不需要它。

尽管如此,我确实倾向于使用“基元”的显式输入,例如>int。但老实说,这更像是一种习惯,而不是一种有意识的决定。但是,对于数字,如果您忘记将string添加到要作为m键入的文字数字,那么类型推断会让您感到困惑,这很容易做到,但编译器赢了“ t让你意外失去精确度,这不是一个实际问题。事实上,如果我在任何地方使用decimal它会在大型应用程序中更改数量,从整数到十进制数字更容易。

var的另一个优势是:它允许快速实验,而不必强制您更新各地的类型以反映您的更改。如果我想将上面的示例更改为var,我可以简单地更改我的实现,并且使用Dictionary<Customer,Order>[]调用它的所有代码将继续工作(好吧,至少是变量声明)。

答案 2 :(得分:8)

在这种情况下它没有,这当然是我的主观意见。我只在类型位于同一行的其他位置时使用它,如:

var words = new List<String>();

我喜欢var,但我不会像你的例子那样使用它,因为它不清楚类型是什么。

答案 3 :(得分:5)

减少噪音/冗余的意义上更清晰。编译器都可以通过words语句轻松推导出new[] { ... }的类型。因此,使用var代替string[],因为后者可能会使代码混乱。

透明度的意义上,它更清晰。您可以将实际值与任何其他类型的实例交换,只要它是可枚举类型即可。如果您没有使用var,则必须更改示例中的两个声明语句。

它更清晰,因为它迫使你使用好的变量名。通过使用var,您不能使用类型声明来指示变量的内容,因此您必须使用描述性名称。您只需要声明一次变量,但是您可以多次使用它,因此最好能够通过它的名称来计算变量的内容。从这个角度来看,word对于循环变量名称来说是更好的选择。

请注意,上述推理是从作者的角度出发的。它不一定反映我的个人意见:))

关于您的附录的修改:

正如我之前提到的,您可以交换基础集合类型,而无需更新所有foreach循环。这样可以更轻松地创建和更改代码,但不一定能防止编程错误。在我们引入Word类作为普通字符串的替代后,让我们看看这两种情况:

如果我们使用var关键字,编译器将捕获错误:

var words = new[] { new Word("a"), new Word("b"), null, new Word("d") };

// The compiler will complain about the conversion from Word to string,
// unless you have an implicit converion.
foreach (string word in words)
{
    Console.WriteLine(word);
}

如果我们使用var,代码将编译没有错误,但如果Word类没有错误,程序的输出将完全不同(正确实施ToString()

var words = new[] { new Word("a"), new Word("b"), null, new Word("d") };

foreach (var word in words)
{
    Console.WriteLine(word); // Output will be different.
}

因此,在某些情况下,当您使用var时,可能会引入细微的错误,否则编译器会捕获这些错误。

答案 4 :(得分:3)

此处清洁意味着减少冗余。由于编译器推断对象的类型为string[]是微不足道的,因此将其显式指定为详细信息。然而,正如你所指出的那样,人类阅读代码可能并不那么明显。

答案 5 :(得分:3)

这个例子很差,因为许多证明语法糖的例子往往是 - 句法糖有助于事情变得复杂,但没有人喜欢复杂的例子。

有两种情况您可能想要使用var,还有一种情况需要:

你可能想要的地方:

  1. 当您在探索问题空间时快速切换所涉及的类型时,它在实验代码中非常有用。
  2. 它可以用于复杂的基于泛型的类型,例如IGrouping<int, IEnumerable<IGrouping<Uri, IGrouping<int, string>>>>,尤其适用于复杂查询和枚举操作中的中间状态。
  3. 就我个人而言,我更喜欢在var之上使用复杂的形式,因为它不会花费任何阅读它而不关心确切类型的人(他们可以跳过它认为“复杂的分组类型”) ),但对于那些在没有自己解决问题的情况下照顾的人来说很清楚。

    您需要的地方:

    在处理匿名类型时,代码如下:

    var res = from item in src select new {item.ID, item.Name};    
    foreach(var i in res)    
        doSomething(i.ID, i.Name);
    

    这里res是匿名类型的IEnumerable或IQueryable,我是匿名类型。由于该类型没有名称,因此无法明确声明它。

    在最后一种情况下,它不是语法糖,但实际上是至关重要的。

    一个相关的抱怨,是SharpDevelop曾经拥有它自己的编辑形式的var;可以输入:

    ? words = new string[] { "a", "b", null, "d" };
    

    在分号处,编辑会产生:

    string[] words = new string[] { "a", "b", null, "d" };
    

    这给了(特别是在更复杂的情况下)打字速度和生成显式代码的优势。他们似乎已经放弃了这个,因为var在概念上是相同的,但遗憾的是丢失了显式表单的输入快捷方式。

答案 6 :(得分:2)

使用隐式打字是一个指导原则,而非法律。

你提出的是一个极端的例子,其中隐含的肯定是不理想的。

答案 7 :(得分:2)

时使代码更清晰
  1. 你的课程名称非常长。
  2. 你有linq查询。

答案 8 :(得分:2)

在我开始使用F#之前,我认为我真的不理解C#中隐式类型的好方面。 F#有一种类型推断机制,在某些方面类似于C#中的隐式类型。在F#中,类型推断的使用是代码设计的一个非常重要的方面,即使在简单的示例中也可以使代码更具可读性。学习在F#中使用类型推断有助于我理解这些情况,即隐式类型可以使C#更具可读性,而不是更容易混淆。 (我认为,Linq案件相当明显,但很多情况并非如此。)

我意识到这听起来更像是F#的插件,而不是问题re.C#的答案,但这不是我可以归结为一套简单规则的东西。在思考可读性和维护等内容时,通过新的眼睛学习代码是一种学习。 (那,也许 是一个F#的插件,大声笑。)

答案 9 :(得分:1)

如果您想更明确地使用此代码,我建议扩展新代码而不是删除var:

var words = new string[] { "a", "b", null, "d" };

Resharper将为您提供删除该示例的冗余代码的提示,但如果您愿意,可以关闭这些提示。

使用var的论据是,在许多情况下,局部变量的类型标识符是冗余的,应删除冗余代码。通过删除冗余代码,您可以使实际关注的情况更加清晰,例如,如果要为局部变量强制实施特定的接口类型:

ISpecificInterface foo = new YourImplementation()

答案 10 :(得分:0)

当您需要更改代码时,您必须在较少的地方执行此操作。没有更多的词典或更长的类型声明,不再有Some.Very.Long.Class.With.Very.Long.Path<string> declaration = functionParameter[index]等等。

虽然我同意在小方法以外的其他情况下使用它时可能会让人感到非常困惑。

答案 11 :(得分:0)

在我看来,显式输入的主要好处是只需查看代码变量的类型即可。因此可读性增加了。

隐式打字的主要好处是:

  • 减少程序文本,特别是长类型名称(类,接口),从而提高可读性
  • 当方法的返回类型发生更改时,导致更少的更改。

看起来这两个选项都提高了可读性。

所以我想这取决于您的偏好,也可能取决于您团队中的编程指南。通过IDE中的当前(重构)工具支持,更改类型名称变得更加容易(没有脑子),因此隐式类型减少更改的原因实际上从努力角度消失了。

我建议:做最适合你的事情。没有对错。尝试每种方法一段时间(例如,通过配置您最喜欢的重构工具的选项),然后使用使您作为开发人员的生活更轻松的原因。

答案 12 :(得分:0)

嗯,你已经提到了一个重要的想法 - 过度使用var确实是有害的,而且在实际类型非常简单的情况下,应该这样说。

然而,当处理更大的继承层次结构和模板时,

var会发光。您也可以将其读作“我不在乎 - 只是给我数据”虽然C#中的模板没有C ++对应的表达能力,但它们比Java的泛型更具表现力,这意味着人们如果你必须明确指出确切的类型,可以制作不仅笨拙而且难以定位的构造。

例如 - 想象一下围绕几种DataReader-s的模板包装器,用于与SQL交谈 - 这样你仍然可以高效(调用sproc,获得结果,完成)但没有内务管理的负担(关闭阅读器和连接,重试或错误等)。使用它的代码只调用一个函数,使其具有尽可能短的语法,它将返回一个包装器,它将像C ++中的智能指针一样 - 对于代码来说就像DataReader一样,但也可以处理所有横向的事情。所以看起来很简单:

using (var r = S.ExecuteReader("[MySproc]", params))
{
    while ((+r).Read())
    (
       // get my data and be done
    )
} // at this point wrapper cleans up everything

在这样的情况下,不仅仅是你不关心命名和声明包装器的方式,你甚至不在乎知道它的名字 - 对于你的代码它是无关紧要的。你只需要你的数据并继续:-)而不用处理任何长的声明。

它确实允许您选择何时关注完整类型声明,何时不关注。这不是全部或全部,您会发现自己使用这两种风格。

如果你开始使用lambda表达式,那么你会很高兴拥有它的另一个地方。如果你在C#中使用lambdas,那几乎总是因为你想要一些短的,紧凑的代码,它将运行一次并且它不值得麻烦变成常规方法,或者它取决于来自宿主方法的局部变量。

哦甚至VS编辑器都会为你推断完整类型,提供自动完成并抱怨如果你试图使用它不能做的事情,所以var根本不会破坏类型安全性(新的C ++得到它的var等价物)以及 - 早就应该了)。