在C#中,我无法将long
隐式转换为int
。
long l = 5;
int i = l; // CS0266: Cannot implicitly convert type 'long' to 'int'. An explicit conversion exists (are you missing a cast?)
这会产生错误。这是正确的;如果我这样做,由于错误的截断,我有可能破坏我的数据。如果我决定我知道自己在做什么,那么我总是可以做一个明确的演员,告诉编译器可以截断,我知道最好。
int i = (int)l; // OK
但是,使用foreach
循环时,相同的机制似乎不适用。
IList<long> myList = new List<long>();
foreach (int i in myList)
{
}
编译器甚至不会在这里生成警告,即使它基本上是相同的:未经检查的long
截断到int
,这可能会破坏我的数据。
所以我的问题很简单:为什么这个foreach
不会产生与变量赋值相同的错误?
答案 0 :(得分:30)
更新:这个问题是the subject of my blog in July of 2013。谢谢你提出的好问题!
为什么这个foreach不会产生与变量赋值相同的错误?
“为什么”这些问题很难回答,因为我不知道你问的“真实”问题。因此,我不会回答这个问题,而是会回答一些不同的问题。
规范的哪一部分证明了这种行为的合理性?
正如迈克尔刘的答案正确指出的那样,这是第8.8.4节。
显式转换的重点是代码中的转换必须是显式;这就是为什么我们有施法者;它挥舞着一面大旗,上面写着“这里有明确的转换”。这是C#中少数几次在代码中不存在显式转换的情况之一。哪些因素促使设计团队无形地插入“明确”的转换?
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)
是如此常见,大部分时间我们都会对正确的代码发出警告。这会给每个编译过“警告错误”的人带来麻烦,而且一般来说,对代码发出警告是不好的,这可能有点臭,但实际上是正确的。我们决定不采取警告。
在其他情况下,C#编译器会无形地插入显式转换吗?
是。事实上有人在这个问题发生后几个小时就问了一个问题:
Compiler replaces explicit cast to my own type with explicit cast to .NET type?
还有一些非常模糊的互操作方案,其中也插入了显式转换。
答案 1 :(得分:13)
如C#4.0规范的§8.8.4中所定义,表单的foreach
语句
foreach (V v in x) embedded-statement
扩展为
{
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current; // <-- note the explicit cast to V
embedded-statement
}
}
finally {
… // Dispose e
}
}
其中C
是“集合类型”,T
是从x
推断的“元素类型”。
转换为V
(在您的情况下为int
)是允许您的示例编译的内容。
转换为V
的可能原因:在C#1.0中,在将泛型添加到语言之前,通常在枚举像ArrayList
这样的集合时需要显式转换,因为编译器无法自动计算出集合中的值类型。
答案 2 :(得分:5)
简单的答案是foreach
在幕后做了明确的演员。另一个例子:
public class Parent { }
public class Child : Parent { }
IList<Parent> parents = new List<Parent>()
{
new Parent()
};
foreach (Child child in parents) { }
这也不会产生编译器错误,但会在运行时抛出InvalidCastException
。