迭代变量的类型不同于集合?

nan*_*nan 7 c# foreach nullable

我有一个可以为空的int集合.

为什么编译器允许迭代变量的类型为int而不是int??

        List<int?> nullableInts = new List<int?>{1,2,3,null};
        List<int> normalInts = new List<int>();


        //Runtime exception when encounter null value
        //Why not compilation exception? 
        foreach (int i in nullableInts)
        {
         //do sth
        }
Run Code Online (Sandbox Code Playgroud)

当然我应该注意我迭代的内容但如果编译器训斥我​​会很好:)就像这里:

        foreach (bool i in collection)
        {
          // do sth 
        }

       //Error 1 Cannot convert type 'int' to 'bool'
Run Code Online (Sandbox Code Playgroud)

Dan*_*Tao 3

更新

好的,最初我说“编译器将强制转换添加到foreach循环中”。这并不严格准确:它并不总是添加强制转换。这就是真正发生的事情。

首先,当你有这个foreach循环时:

foreach (int x in collection)
{
}
Run Code Online (Sandbox Code Playgroud)

...这里是编译器创建内容的基本轮廓(伪 C# 语言):

int x;
[object] e;
try
{
    e = collection.GetEnumerator();
    while (e.MoveNext())
    {
        x = [cast if possible]e.Current;
    }
}
finally
{
    [dispose of e if necessary]
}
Run Code Online (Sandbox Code Playgroud)

什么?我听到你说。你是什​​么意思[object]?”

这就是我的意思。该foreach循环实际上不需要任何接口,这意味着它实际上有点神奇。它只要求被枚举的对象类型公开一个GetEnumerator方法,而该方法又必须提供某种类型的实例,该实例提供 aMoveNext和 aCurrent属性。

所以我这么写是[object]因为 的类型e不一定必须是 的实现IEnumerator<int>,甚至IEnumerator- 这也意味着它不一定必须实现IDisposable(因此是该[dispose if necessary]部分)。

为了回答这个问题,我们关心的代码部分是我编写的部分[cast if possible]。显然,由于编译器不需要实际的IEnumerator<T>或IEnumerator实现,因此不能将 的类型e.Current假定为T或object介于两者之间的任何类型。e.Current相反,编译器根据GetEnumerator编译时返回的类型来确定 的类型。然后会发生以下情况:

  1. 如果类型是局部变量的类型(x在上面的示例中),则使用直接赋值。
  2. 如果类型可转换为局部变量的类型(我的意思是,存在从 类型e.Current到 类型的合法转换x),则插入转换。
  3. 否则,编译器将引发错误。

因此,在枚举 a 的场景中List<int?>,我们进入步骤 2,编译器会看到该List<int?>.Enumerator类型的Current属性属于类型int?,可以显式转换为int。

因此该行可以编译为等价的:

x = (int)e.Current;
Run Code Online (Sandbox Code Playgroud)

现在,explicit操作员是什么样子的Nullable<int>?

根据反射镜:

public static explicit operator T(T? value)
{
    return value.Value;
}
Run Code Online (Sandbox Code Playgroud)

因此,据我所知,Kent 描述的行为(int)e.Current只是一种编译器优化:显式强制转换是内联的。

作为对您问题的一般回答,我坚持我的主张,即编译器会foreach在需要时在循环中插入强制转换。


原答案

编译器会自动在foreach循环中需要的地方插入强制转换,原因很简单,在泛型之前没有IEnumerable<T>接口,只有IEnumerable*。该IEnumerable接口公开一个IEnumerator,而后者又提供对Currenttype 属性的访问object。

因此,除非编译器为您执行强制转换,否则在过去,您可以使用的唯一方法foreach是使用类型的局部变量object,这显然会很糟糕。

*实际上,foreach根本不需要任何接口——只需要方法和带有 a和 a 的GetEnumerator附带类型。MoveNextCurrent