更新:这个问题是我2013年7月博客的主题 http://ericlippert.com/2013/07/22/why-does-a-foreach-loop-silently-insert-an-explicit-conversion/。感谢您提出的好问题!
为什么这个 foreach 不会产生与变量赋值相同的错误?
“为什么”问题很难回答,因为我不知道你问的“真正”问题。因此,我不会回答这个问题,而是回答一些不同的问题。
规范的哪一部分证明了这种行为的合理性?
正如 Michael Liu 的回答正确指出的那样,它是第 8.8.4 节。
整个点explicit转换是转换必须是explicit在代码中;这就是为什么我们有强制转换运算符;它挥舞着一面大旗,上面写着“这里有一个显式转换”。这是 C# 中代码中不存在显式转换的少数情况之一。哪些因素促使设计团队无形中插入“显式”转换?
The foreach
循环是在泛型之前设计的。
ArrayList myList = new ArrayList();
myList.Add("abc");
myList.Add("def");
myList.Add("ghi");
你不想说:
foreach(object item in myList)
{
string current = (string)item;
在没有仿制药的世界里,你必须提前知道列表中有哪些类型,以及你几乎总是具备这些知识。但该信息并未在类型系统中捕获。因此,你必须以某种方式告诉编译器,你可以通过说
foreach(string item in myList)
这是您对编译器的断言,即该列表充满字符串,就像强制转换是对特定项目是字符串的断言一样。
您完全正确,这在泛型世界中是一个错误特征。因为现在改变它会破坏它,所以我们坚持下去。
这个功能相当令人困惑;当我第一次开始 C# 编程时,我假设它具有如下语义:
while(enumerator.MoveNext())
{
if (!(enumerator.Current is string) continue;
string item = (string)enumerator.Current;
也就是说,“对于此列表中字符串类型的每个对象,执行以下操作”,而实际上是“对于此列表中的每个对象断言该项目是字符串并执行以下操作...”(如果前者是你真正想要什么然后使用OfType<T>()
扩展方法。)
这个故事的寓意是:当你在版本 2 中大规模更改类型系统时,语言最终会出现奇怪的“遗留”功能。
在使用泛型的现代代码中,编译器是否应该针对这种情况发出警告?
我考虑过。我们的研究表明
foreach(Giraffe in listOfMammals)
is 如此常见大多数时候我们都会对正确的代码发出警告。这给每个打开“警告作为错误”进行编译的人带来了麻烦,而且一般来说,在代码上发出警告是很糟糕的,是的,可能有点臭,但是实际上是正确的。我们决定不再追究该警告。
是否存在 C# 编译器不可见地插入显式转换的其他情况?
是的。事实上,就在这件事发生几个小时后,就有人问了一个问题:
编译器将显式转换为我自己的类型替换为显式转换为 .NET 类型? https://stackoverflow.com/questions/16425856/compiler-replaces-explicit-cast-to-my-own-type-with-explicit-cast-to-net-type/16427263#16427263
在一些极其模糊的互操作场景中,也会插入显式转换。