33

在 C# 中,我不能将 a 隐式转换longint.

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)
{
}

编译器甚至不会在此处生成警告,即使它本质上是同一件事:未经检查地将 a 截断为 a longint这很可能会破坏我的数据。

所以我的问题很简单:为什么这foreach不会产生与变量赋值相同的错误?

4

3 回答 3

30

更新:这个问题是我 2013 年 7 月博客的主题。谢谢你的好问题!

为什么这个 foreach 不会产生与变量赋值相同的错误?

“为什么”问题很难回答,因为我不知道您要问的“真实”问题。所以我不会回答这个问题,而是回答一些不同的问题。

规范的哪一部分证明了这种行为?

正如迈克尔刘的回答正确指出的那样,它是第 8.8.4 节。

显式转换的全部意义在于转换必须在代码中显式;这就是我们有 cast 运算符的原因;它挥舞着一面大旗,上面写着“这里有一个明确的转换”。这是 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# 编译器不可见地插入显式转换的其他情况?

是的。事实上,就在这个问题之后几个小时,有人问了一个问题:

编译器将显式转换为我自己的类型替换为显式转换为 .NET 类型?

有一些非常晦涩的互操作场景也插入了显式转换。

于 2013-05-07T15:22:35.207 回答
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,因为编译器无法自动确定集合中值的类型。

于 2013-05-07T14:56:32.787 回答
5

简单的答案是foreach在幕后进行明确的演员表。另一个例子:

    public class Parent { }
    public class Child : Parent { }

    IList<Parent> parents = new List<Parent>()
    {
        new Parent()
    };
    foreach (Child child in parents) { }

这也不会产生编译器错误,但会InvalidCastException在运行时抛出。

于 2013-05-07T14:49:41.997 回答