我有一个可以为空的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
}
当然我应该注意我迭代的内容但是如果编译器训斥我会很好:)就像这里:
foreach (bool i in collection)
{
// do sth
}
//Error 1 Cannot convert type 'int' to 'bool'
答案 0 :(得分:4)
因为C#编译器为你取消引用Nullable<T>
。
如果你写这段代码:
var list = new List<int?>()
{
1,
null
};
foreach (int? i in list)
{
if (!i.HasValue)
{
continue;
}
Console.WriteLine(i.GetType());
}
foreach (int i in list)
{
Console.WriteLine(i.GetType());
}
C#编译器生成:
foreach (int? i in list)
{
if (i.HasValue)
{
Console.WriteLine(i.GetType());
}
}
foreach (int? CS$0$0000 in list)
{
Console.WriteLine(CS$0$0000.Value.GetType());
}
请注意Nullable<int>.Value
的显式解除引用。这证明了Nullable<T>
结构在运行时中的根深蒂固。
答案 1 :(得分:3)
好的,最初我说过“编译器将强制转换添加到foreach
循环。”这不是严格准确:它不会始终添加强制转换。这是真正发生的事情。
首先,当你有这个foreach
循环:
foreach (int x in collection)
{
}
...这是编译器创建的基本概要(伪C#):
int x;
[object] e;
try
{
e = collection.GetEnumerator();
while (e.MoveNext())
{
x = [cast if possible]e.Current;
}
}
finally
{
[dispose of e if necessary]
}
什么? 我听到你说。 [object]
是什么意思?“
这就是我的意思。 foreach
循环实际上需要无接口,这意味着它实际上有点神奇。它只要求枚举的对象类型公开GetEnumerator
方法,而该方法必须提供某种类型的实例,该实例提供MoveNext
和Current
属性。
所以我写了[object]
因为e
的类型不一定是IEnumerator<int>
的实现,甚至是IEnumerator
的实现 - 这也意味着它没有'必须实施IDisposable
(因此[dispose if necessary]
部分)。
我们为回答这个问题而关心的部分代码是我写[cast if possible]
的部分。显然,由于编译器不需要实际的IEnumerator<T>
或IEnumerator
实现,因此e.Current
的类型不能假定为T
,object
或任何内容之间。相反,编译器根据e.Current
在编译时返回的类型确定GetEnumerator
的类型。然后发生以下情况:
x
),则使用直接赋值。e.Current
的类型到x
的类型存在合法的演员表),插入一个演员。因此,在枚举List<int?>
的情况下,我们进入第2步,编译器发现List<int?>.Enumerator
类型的Current
属性类型为int?
,可以明确地转换为int
。
因此该行可以编译为相当于:
x = (int)e.Current;
现在,explicit
运算符对于Nullable<int>
是什么样的?
根据反射器:
public static explicit operator T(T? value)
{
return value.Value;
}
因此,就我所知,the behavior described by Kent只是一个编译器优化:(int)e.Current
显式转换是内联的。
作为对你的问题的一般回答,我坚持认为编译器会在需要的foreach
循环中插入强制转换。
编译器会自动在foreach
循环中的所需位置插入强制转换,原因很简单,因为在泛型之前没有IEnumerable<T>
接口,只有IEnumerable
*。 IEnumerable
界面会显示IEnumerator
,Current
会提供对object
类型的foreach
属性的访问权限。
因此,除非编译器为您执行了演员表,否则在过去,您使用object
的唯一方法就是使用GetEnumerator
类型的局部变量,这显然会“我们很糟糕。
*实际上,foreach
doesn't require any interface at all - 只有MoveNext
方法以及带有Current
和{{1}}的附带类型。
答案 2 :(得分:3)
您正在观察的行为是根据 8.8.4 C#语言规范的foreach语句部分。本节定义foreach
语句的语义如下:
[...]上述步骤如果成功,则明确地生成集合类型
C
,枚举器类型E
和元素类型T
。表单的foreach
声明foreach (V v in x) embedded-statement
然后扩展为:
{ E e = ((C)(x)).GetEnumerator(); try { V v; while (e.MoveNext()) { v = (V)(T)e.Current; embedded-statement } } finally { // Dispose e } }
根据规范中定义的规则,在您的示例中,集合类型将为List<int?>
,枚举器类型将为List<int?>.Enumerator
元素类型为int?
。
如果您将此信息填写到上面的代码段中,您会看到通过调用Nullable<T> Explicit Conversion (Nullable<T> to T)
将int?
显式转换为int
。如Kent所述,这个显式强制转换运算符的实现只是返回Nullable<T>.Value
属性。