8

这个问题源于一个错误,我在其中迭代了一个集合Int64并意外地做了foreach (int i in myCollection). 我试图调试令人费解的问题,即当我执行 linq 查询时,i它不是myCollection.

这是一些令我惊讶的代码:

Int64 a = 12345678912345;

Console.Write((int)a);

我希望编译器会给我一个错误。通常的情况是不存在隐式强制转换。但是不,它根本不介意这一点。连警告都没有!

(int)a顺便说一下的输出值为1942903641

我很想知道为什么在没有任何警告的情况下允许演员阵容,以及它是如何得出这个值的。有任何想法吗?

4

6 回答 6

15

默认情况下,不会检查此类转换。您必须明确要求:

   Console.Write(checked((int)a));    // Kaboom!

检查转换可以通过 C# 编译器的/checked 选项全局启用。在我的书中,项目模板没有为 Debug 构建打开此选项是一个疏忽,溢出可能非常难以诊断。

然而,没有什么是您无法解决的,只需使用项目 > 属性 > 构建选项卡 > 高级按钮 > 勾选“检查算术上溢/下溢”选项。并注意你的 foreach 循环现在是如何用 OverflowException 轰炸的。请记住,当它发生时你会盯着它看几分钟 :) 这不是一个非常便宜的检查,所以你会想把它留给 Release 构建。

于 2013-11-01T14:50:44.300 回答
6

默认情况下,C# 在处理数字时不检查溢出。这包括诸如从int.MaxValue加到int.MinValue乘法的换行,以及当你将longs 转换为ints 时。要控制这一点,请使用checkedandunchecked关键字/checked编译器选项。

该值1942903641是您long被截断为int. 它来自long值的 32 个最低有效位,作为二进制补码有符号整数。

使用 时foreach,重要的是要知道,如果您声明的类型与可枚举的类型不匹配,它会将其视为您已转换为该类型。foreach (int i in myCollection)编译成类似的东西int i = (int)myEnumerator.Current;,不是int i = myEnumerator.Current;。您可以使用它foreach (var i in myCollection)来避免将来出现此类错误。var 建议forandforeach语句中使用 for 循环变量。

您可以在以下示例中看到各种事情的结果(十六进制输出用于更清楚地显示截断:它们具有相同的结束数字,int只是缺少一些更有效的数字):

checked
{
    Int64 a = 12345678912345;
    Console.WriteLine(a.ToString("X"));
    Console.WriteLine((a % ((long)uint.MaxValue + 1L)).ToString("X"));
    try
    {
        Console.WriteLine(((int)a).ToString("X")); // throws exception
    }
    catch (Exception e)
    {
        Console.WriteLine("It threw! " + e.Message);
    }
}
unchecked
{
    Int64 a = 12345678912345;
    Console.WriteLine(a.ToString("X"));
    Console.WriteLine((a % (long)Math.Pow(2, 32)).ToString("X"));
    Console.WriteLine(((int)a).ToString("X"));
}

这输出:

B3A73CE5B59
73CE5B59
It threw! Arithmetic operation resulted in an overflow.
B3A73CE5B59
73CE5B59
73CE5B59
于 2013-11-01T14:52:42.187 回答
3

默认情况下,对于整数类型的算术运算,溢出检查是关闭的。

您可以通过将代码放入“已检查”部分来启用它:

        Int64 a = 12345678912345;
        checked
        {
            Console.Write((int)a);
        }

您可以通过更改编译器选项来实现相同的目的。

于 2013-11-01T14:50:57.477 回答
1

在现代版本的 VS(自 VS 2003 左右?)中,算术上溢/下溢检查默认关闭。您可以在项目的属性 -> 构建 -> 高级 ->“检查算术上溢/下溢”中更改它。

于 2013-11-01T14:52:23.393 回答
1

发生强制转换的原因foreach是它起源于 C# 1 - 在泛型之前。

它被定义为(C# 语言规范 v5的第 8.8.4 节):

形式的 foreach 语句

foreach (V v in x) embedded-statement

然后扩展为:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        while (e.MoveNext()) {
            V v = (V)(T)e.Current;
            embedded-statement
        }
    }
    finally {
        … // Dispose e
    }
}

如您所见,在foreach第一行自动获得显式转换。

于 2013-11-01T14:55:30.507 回答
0

您的值12345678912345被截断为 32 位整数。十六进制更容易理解:

  • 12345678912345以 10 为底是b3a73ce5b59十六进制。
  • b3a73ce5b59截断为 32 位是73ce5b59(保留最低有效的 8 个十六进制数字)。
  • 73ce5b59十六进制1942903641以 10 为底。

至于为什么您没有收到任何错误或警告:

  • 您的转换不涉及编译时常量,因此编译器不会进行任何静态检查(在这种简单的情况下,它可以,但通常不会)。
  • 默认情况下,C#longint显式转换会生成conv.i4CIL 指令,该指令会截断值而不会在溢出时引发异常。使用checked语句或表达式,或使用/checkedswitch 进行编译将使 C# 编译器发出conv.ovf.i4代替,这会在溢出时引发异常。
于 2013-11-01T15:00:04.073 回答