C#类型转换不一致?

Jan*_*aus 33 c# types casting

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

long l = 5;
int i = l;  // CS0266: Cannot implicitly convert type 'long' to 'int'. An explicit conversion exists (are you missing a cast?)
Run Code Online (Sandbox Code Playgroud)

这会产生所述错误.这是正确的; 如果我这样做,由于错误的截断,我有可能破坏我的数据.如果我决定我知道自己在做什么,那么我总是可以做一个明确的演员,告诉编译器可以截断,我知道最好.

int i = (int)l;  // OK
Run Code Online (Sandbox Code Playgroud)

但是,使用foreach循环时,相同的机制似乎不适用.

IList<long> myList = new List<long>();
foreach (int i in myList)
{
}
Run Code Online (Sandbox Code Playgroud)

编译器甚至不会在这里生成警告,即使它基本上是相同的事情:未经检查的截断a long到a int,这可能会很好地破坏我的数据.

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

Eri*_*ert 30

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

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

"为什么"这些问题很难回答,因为我不知道你问的"真实"问题.因此,我不会回答这个问题,而是会回答一些不同的问题.

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

正如迈克尔刘的答案正确指出的那样,这是第8.8.4节.

一个整点明确的转换是在转换必须是明确的代码; 这就是为什么我们有施法者; 它挥舞着一面大旗,上面写着"这里有明确的转换".这是C#中少数几次在代码中不存在显式转换的情况之一.哪些因素促使设计团队无形地插入"明确"的转换?

foreach循环被仿制药之前设计的.

ArrayList myList = new ArrayList();
myList.Add("abc");
myList.Add("def");
myList.Add("ghi");
Run Code Online (Sandbox Code Playgroud)

你不想说:

foreach(object item in myList)
{
    string current = (string)item;
Run Code Online (Sandbox Code Playgroud)

在没有泛型的世界中,您必须提前知道列表中的类型,并且您几乎总是拥有这些知识.但是这种信息不会在类型系统中捕获.因此,你必须以某种方式告诉编译器,并且你这样说

foreach(string item in myList)
Run Code Online (Sandbox Code Playgroud)

这是你对编译器的断言,列表中充满了字符串,就像强制转换是特定项是字符串的断言一样.

你是完全正确的,这是一个带有泛型的世界中的错误.既然现在要改变它,我们就会坚持下去.

这个功能很混乱; 当我第一次开始编程C#时,我认为它具有类似的语义:

while(enumerator.MoveNext())
{
    if (!(enumerator.Current is string) continue;
    string item = (string)enumerator.Current;
Run Code Online (Sandbox Code Playgroud)

也就是说,"对于此列表中类型字符串的每个对象,执行以下操作",当它确实是"对于此列表中的每个对象断言该项目是字符串并执行以下操作..."(如果前者是你真正想要的是然后使用OfType<T>()扩展方法.)

这个故事的寓意是:当您在版本2中大规模更改类型系统时,语言最终会出现奇怪的"遗留"功能.

编译器是否应该在使用泛型的现代代码中为此案例生成警告?

我考虑过了.我们的研究表明

foreach(Giraffe in listOfMammals)
Run Code Online (Sandbox Code Playgroud)

如此常见,以至于大多数时候我们都会对正确的代码发出警告.这会给每个编写了"警告错误"的人带来麻烦,而且一般来说,对代码发出警告是不好的,这可能有点臭,但实际上是正确的.我们决定不追究警告.

是否存在C#编译器无形插入显式转换的其他情况?

是.事实上有人在这个问题发生后几个小时就问了一个问题:

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

还有一些非常模糊的互操作场景,其中也插入了显式转换.


Mic*_*Liu 13

如C#4.0规范的§8.8.4中所定义,表单的foreach声明

foreach (V v in x) embedded-statement
Run Code Online (Sandbox Code Playgroud)

扩大到

{
    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
    }
}
Run Code Online (Sandbox Code Playgroud)

其中C是"集合类型",T是从中推断出的"元素类型" x.

转换为V(int在您的情况下)是允许您的示例编译的内容.

转换的可能原因V:在C#1.0中,在将泛型添加到语言之前,通常在枚举类似的集合时通常需要显式转换ArrayList,因为编译器无法自动计算集合中的值的类型.


Jos*_*irn 5

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

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

    IList<Parent> parents = new List<Parent>()
    {
        new Parent()
    };
    foreach (Child child in parents) { }
Run Code Online (Sandbox Code Playgroud)

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