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

9

我正在阅读的一本书中写道,隐式类型转换使以下代码比不使用 var 关键字更清晰:

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

foreach (var item in words)
{
    Console.WriteLine(item);
}
似乎相反的情况才是真的:如果您使用string,那么代码读者将立即知道它在foreach循环中是一个字符串,而不必在代码中查找变量定义的位置。
隐式类型转换如何使上面的代码更清晰?
附录
书名为C # 3.0 - Die Neuerungen.快速+简洁,书籍为德文版,实际文本如下:
var关键字也可以在foreach循环中使用,从而使代码更加简单易懂。特别是在使用复杂类型时,可以避免编程错误。
这是我的翻译:
var关键字也可以在foreach循环中使用,从而使代码更简单易于创作。特别是在使用复杂类型时,这可以防止编程错误。
现在更仔细地阅读它,他实际上声明var在foreach循环中使创建代码更容易,但不一定更易于阅读.

5
个人而言,每当我在 C# 中看到 var 关键字时,就会感觉代码不够规范。在我看来,那本书的作者是错的,除非有更多的上下文可以将这一点解释清楚。 - tdammers
4
在这种情况下,你是正确的,但是var k = new MyClass()比MyClass k = new MyClass()更好。 - Srinivas Reddy Thatiparthy
13
你真的更喜欢使用Dictionary<string, int> map = new Dictionary<string, int>();而不是var map = new Dictionary<string, int>();吗?我知道,我不喜欢。 - Brian Rasmussen
6
@Brian:是的,事实上我需要。IDictionary<string, int> map = new Dictionary<string, int>();更好,因为它表达了我对于它是一个字符串/整数字典的兴趣,但实际实现的选择是次要的。强类型编程在多个场合都拯救了我,我更喜欢编译器向我报错而不是我的上司。 - tdammers
6
除了在某些情况下可能存在的可读性问题之外,我还没有看到由类型推断引起的任何问题。@ tdammers:“强类型编程已经救过我的命,我更喜欢编译器警告我而不是老板”--如果你在使用var时出错,编译器仍然会警告你;它仍然是强类型的。 - Mark Simpson
显示剩余8条评论
13个回答

12

就我个人而言,我同意你的看法。虽然我不确定是否会使用更清晰的词语,但在某些情况下,var关键字肯定可以使其更加简洁,例如:

var myClass = new ExtremelyLongClassNameIWouldntWantToTypeTwice();

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();
我不知道你怎么看,但我发现从中提取所需信息要困难得多。这主要是因为实际信息(变量名)所在的位置更靠右,需要更长时间才能找到它,而且还被所有那些疯狂的<>遮挡。实际类型是毫无价值的,它是胡言乱语,特别是因为要理解它,你需要知道这些通用类型的作用。
如果你对某个方法的作用或变量包含的内容不确定,仅从名称就可以判断,那么你应该给它一个更好的名称。这比看到它是什么类型要有价值得多。
显式类型声明不应该是必需的,如果需要,那么你的代码存在问题,而不是类型推断存在问题。它不可能是必需的,因为其他语言似乎也不需要它。
话虽如此,我确实倾向于对'原始数据类型'进行显式类型声明,比如intstring。但说实话,这更多是一种习惯,而不是一个有意识的决定。不过对于数字,如果忘记将m添加到字面数字中,以便将其视为decimal类型,类型推断可能会让你出错,但编译器不会让你意外失去精度,所以这并不是一个实际的问题。事实上,如果我在所有地方都使用var,那么在我正在处理的大型应用程序中,数量从整数到小数的变化将变得更加容易。
这又是var的另一个优点:它允许快速实验,而不需要强制更新所有类型以反映你的更改。如果我想将上面的示例更改为Dictionary<Customer,Order>[],我只需更改我的实现,所有使用var调用它的代码将继续工作(至少变量声明会)。

2
命名很难,而且根据定义总是模棱两可的。你认为类型信息是无关紧要的“胡言乱语”的论点令人困惑。如果不了解所涉及的代码(类型),如何理解任何代码片段的行为?如果被迫做出假设,又如何确定类型呢?Var 使代码更难阅读,因为它删除了重要信息。只有在必要的情况下才应该使用它。 - Dan

9

在这种情况下,这只是我的主观看法。当类型在同一行的其他地方时,我才会使用它,例如:

var words = new List<String>();

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


这是我之前没有考虑过的。当我需要同时声明和创建新对象时,我可能会开始使用它。 - Chris
+1,这也是我做的一样的事情。这样变量的类型仍然可以通过阅读该行来明显,而且您不必输入两次。 - Sasha Chedygov

5
这是一段关于使用var关键字的解释。它可以使代码更清晰,减少噪音和冗余。通过new[] { ... }语句,编译器和开发人员都可以轻松推断出words的类型。因此,使用var代替string[]可以避免代码视觉上的混乱。
使用var还可以使代码更透明。只要是可枚举类型,就可以将实际值与任何其他类型的实例进行交换。如果不使用var,则需要在示例中更改两个声明语句。
使用var可以强制使用良好的变量命名,因为不能使用类型声明来表示变量的内容,所以必须使用描述性名称。变量只声明一次,但可能会多次使用,因此最好能够通过名称确定变量的内容。
请注意,以上推理是从作者的角度进行的,不一定反映我的个人观点 :)
关于您的补充说明:
正如我之前提到的,您可以交换底层集合类型,而无需更新所有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可能会引入微妙的错误,否则这些错误本应该被编译器捕捉到。

“它强制你使用良好的变量名称。”我个人向您保证,它并没有这样做。但我理解您的意思。 - Heinzlmaen

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曾经拥有自己的while-editting形式的var;可以键入:

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

在分号处,编辑器将产生:

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

在更复杂的情况下,这给予了打字速度和生成显式代码的优势。现在似乎已经放弃了这种方法,因为var在概念上可以实现相同的功能,但失去了显式形式的类型快捷方式令人遗憾。


3

这里的“清晰”意味着更少的冗余。因为编译器很容易推断出对象的类型是string[],所以明确指定它被认为是啰嗦的。然而,正如你所指出的那样,对于阅读代码的人来说可能不太明显。


2

使用隐式类型是一项指南,而不是法律。

你提出的是一个极端的例子,隐式类型显然不是理想选择。


1
我认为示例中的变量名item并不理想;word会更好,因为它让你能够猜测到类型是string - Niels van der Rest

2

当:

  1. 你有一个非常长的类名时。
  2. 你有Linq查询时。

这将使代码更清晰。


我认为提问者特别指的是问题中给出的代码。 - Noldorin

2
我认为直到我开始使用F#,我才真正理解了C#中隐式类型的好处。F#具有一种类型推断机制,某些方面类似于C#中的隐式类型。在F#中,使用类型推断是代码设计中非常重要的一个方面,即使在简单的示例中也可以使代码更易读。学习在F#中使用类型推断帮助我理解那些情况下隐式类型可以使C#更易读,而不是更加混乱。(LINQ案例我觉得相当明显,但许多情况并非如此。)
我意识到这听起来更像是在宣传F#而不是回答关于C#的问题,但我无法将其归结为一组简单的规则。这是一种通过新的视角看待代码的学习,思考可读性和维护等问题。(这也许有点像F#的宣传,哈哈。)

现在你提到了,确实有点道理,就像治愈被旧代码堆积造成的创伤一样 :-) 并且意识到编译器很久以前就能够定位类型,但那只是为了减少我们的 RSI 而没有暴露出来。 - ZXX

1
如果您想让这段代码更加明确,我建议扩展“new”而不是删除“var”。
var words = new string[] { "a", "b", null, "d" };

Resharper会给你提示,让你通过这个例子来删除冗余代码,但如果你愿意,你可以关闭这些提示。

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

ISpecificInterface foo = new YourImplementation()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接