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

在我正在阅读的一本书中,它指出隐式类型使得以下代码比不使用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使得代码更容易创建,但不一定更容易阅读。

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

 var myClass = new ExtremelyLongClassNameIWouldntWantToTypeTwice(); 

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

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

静态类型语言中的类型推断应该是常态,而不是例外; 它可以减少视觉混乱和冗余,同时在明确指定类型时使您的意图更清晰,因为您需要较少派生的类型( IList list = new List(); )。

有些人可能会反对像这样的var

 var c = SomeMethod(); 

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

改进:

 var customer = SomeMethod(); 

更好:

 var customer = GetCustomer(); 

让我们尝试显式输入:

 Customer customer = GetCustomer(); 

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

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

 var ordersPerCustomer = GetOrdersPerCustomer(); 

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

让我们看一下显式版本:

 IEnumerable> ordersPerCustomer = GetOrdersPerCustomer(); 

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

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

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

也就是说,我倾向于使用'primitives'的显式类型,比如intstring 。 但说实话,这更像是一种习惯,而不是一种有意识的决定。 但是对于数字,如果你忘记将m添加到你想要输入为decimal的文字数字,那么类型推断会让你感到困惑,这很容易做到,但是编译器不会让你意外地失去精度所以这不是一个实际问题。 事实上,如果我在任何地方使用var ,它会在大型应用程序中对数量进行更改,从整数到十进制数字更容易。

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

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

 var words = new List(); 

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

更少的噪音/冗余感来看,它更清晰。 编译器开发人员可以通过new[] { ... }语句轻松推导出words的类型。 所以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时可能会引入细微的错误,否则会被编译器捕获。

这里清洁意味着更少的冗余 由于编译器推断对象的类型是string[]是微不足道的,因此明确指定它会被认为是冗长的。 然而,正如你所指出的,人类阅读代码可能并不那么明显。

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

有两种情况你可能想要使用var ,还有一种情况你必须:

你可能想要它的地方:

  1. 当您在探索问题空间时快速切换所涉及的类型时,它在实验代码中非常有用。
  2. 它可用于复杂的基于generics的类型,例如IGrouping>>> ,这尤其适用于复杂查询和枚举操作中的中间状态。

就个人而言,我更喜欢使用复杂的forms而不是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曾经拥有它自己的编辑forms的var; 可以输入:

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

在分号处,编辑会产生:

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

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

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

你提出的是一个极端的例子,隐含的当然不是理想的。

它使代码更清晰

  1. 你有一个名字很长的class级。
  2. 你有linq查询。

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

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

如果你想让这段代码更明确,我建议扩展新代码而不是删除var:

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

Resharper将为您提供删除冗余代码的提示,但您可以根据需要关闭这些提示。

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

 ISpecificInterface foo = new YourImplementation() 

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

虽然我同意,当它用于其他情况而不是小方法时,它可能会变得非常混乱。

显式类型的主要好处在于我认为只需查看代码变量的类型即可。 因此可读性增加了。

隐式类型的主要好处是:

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

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

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

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

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

然而,当处理更大的inheritance层次结构和模板时,var会发光。 您也可以将其读作“我不在乎 – 只给我数据”虽然C#中的模板没有C ++对应的表达能力,但它们具有比Java的generics更强的表达力,这意味着人们可以制作哪些结构如果你必须明确指出确切的类型,不仅是尴尬而且难以定位。

例如 – 想象一下围绕几种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,那几乎总是因为你想要一些短的,紧凑的代码,它将运行一次并且它不值得变成常规方法或者它取决于来自host方法的局部变量。

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