10

在我正在阅读的一本书中,它指出隐式键入使以下代码比不使用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。

这是我的翻译:

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

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

4

13 回答 13

12

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

var myClass = new ExtremelyLongClassNameIWouldntWantToTypeTwice();
于 2010-08-06T10:03:36.173 回答
12

我认为奇怪的是,只有 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();

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

也许一些反对者var可能会承认,在上面的例子中,var并没有什么坏处。但是,如果一个方法不返回一个简单且众所周知的类型,如Customer, 或Order,而是一些已处理的值,如某种 Dictionary 怎么办?就像是:

var ordersPerCustomer = GetOrdersPerCustomer();

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

让我们看一下显式版本:

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

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

如果您在任何时候都不确定方法的作用或变量包含的内容,仅从名称中,您应该给它一个更好的名称。这比看它是什么类型更有价值。

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

也就是说,我确实倾向于对“原始”使用显式类型,例如intand string。但老实说,这更多是一种习惯,而不是有意识的决定。但是,对于数字,如果您忘记将 添加m到要键入为的文字数字中,类型推断可能会让您感到困惑decimal,这很容易做到,但是编译器不会让您意外丢失精度,所以它不是一个实际的问题。事实上,如果我在var任何地方都使用它,那么在我处理的大型应用程序中从整数到十进制数的数量变化会容易得多。

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

于 2010-08-07T00:34:09.680 回答
9

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

var words = new List<String>();

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

于 2010-08-06T10:04:05.123 回答
5

从减少噪音/冗余的意义上说,它更清晰。编译器和开发人员都可以通过语句words轻松推断出的类型。So用于代替,因为后者会在视觉上使代码混乱。new[] { ... }varstring[]

在透明的意义上,它更清晰。您可以将实际值与任何其他类型的实例交换,只要它是可枚举类型即可。如果您不使用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,程序的输出将完全不同。WordToString()

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,否则编译器会发现这些错误。

于 2010-08-06T11:05:35.320 回答
3

干净在这里意味着更少的冗余。由于编译器很容易推断出对象的类型是string[],因此显式指定它被认为是冗长的。但是,正如您所指出的,对于阅读代码的人来说,它可能并不那么明显。

于 2010-08-06T10:05:50.413 回答
3

这个例子很糟糕,因为很多例子都证明了句法糖——句法糖在事情复杂的地方有帮助,但没有人喜欢复杂的例子。

在两种情况下您可能想要使用var,而在一种情况下您必须:

你可能想要的地方:

  1. 当您在探索问题空间时快速切换所涉及的类型时,它在实验代码中很有用。
  2. 它对于复杂的基于泛型的类型很有用,例如IGrouping<int, IEnumerable<IGrouping<Uri, IGrouping<int, string>>>>复杂查询和枚举操作中的中间状态尤其会发生这种情况。

就个人而言,我甚至更喜欢使用复杂的形式而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,而 i 是该匿名类型。由于该类型没有名称,因此不可能显式声明它。

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

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

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

在分号处,编辑器会生成:

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

这给了(尤其是在更复杂的情况下)打字速度和生成显式代码的优势。现在他们似乎已经放弃了这个,因为 var 在概念上做了同样的事情,但很遗憾失去了显式表单的打字快捷方式。

于 2010-08-06T11:22:17.357 回答
2

使用隐式类型是一种指导方针,而不是法律。

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

于 2010-08-06T10:04:35.177 回答
2

它使代码更清晰

  1. 你有一个名字很长的类。
  2. 您有 linq 查询。
于 2010-08-06T10:12:58.437 回答
2

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

我意识到这听起来更像是 F# 的插件,而不是对 re.C# 问题的答案,但这不是我可以归结为一组简单的规则的东西。在考虑可读性和维护性等问题时,这是一种学习以新的眼光看待代码。(那个,也许它有点像 F# 的插件,哈哈。)

于 2010-08-06T16:42:02.917 回答
1

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

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

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

使用 var 的理由是,在许多情况下,局部变量的类型标识符是多余的,应该删除多余的代码。通过删除冗余代码,您可以使您真正关心的情况更加清晰,例如,如果您想为局部变量强制执行特定的接口类型:

ISpecificInterface foo = new YourImplementation()
于 2010-08-06T10:17:58.803 回答
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# 中使用 lambda,几乎总是因为您想要一些简短、紧凑的代码,这些代码将运行一次,并且将其转换为常规方法不值得麻烦,或者它依赖于宿主方法中的局部变量。

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

于 2010-08-09T06:13:08.453 回答
0

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

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

于 2010-08-06T10:08:35.853 回答
0

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

隐式类型的主要好处是:

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

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

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

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

于 2010-08-06T11:24:30.157 回答