为什么VS 2017生成GetHashCode没有使用unchecked块

5

我最近发现Visual Studio 2017可以自动生成EqualsGetHashCode的覆盖,但是我想知道为什么GetHashCode实现没有在unchecked块中?

我创建了一个简单的类,有两个公共字符串属性Foo和Bar,生成的GetHashCode实现如下所示。

public override int GetHashCode()
{
    var hashCode = -504981047;
    hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Foo);
    hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Bar);
    return hashCode;
}

我认为未经检查的GetHashCode实现很重要,因为它很可能会溢出,而我们不希望出现任何溢出异常,因为如果它绕过来了也没关系。


1
很遗憾,C#项目模板没有打开溢出检查,所以他们没有考虑到这一点。 - Hans Passant
@HansPassant 您的评论引导我检查如何为 C# 项目打开溢出检查,如果项目已经打开了溢出检查,则 GetHashCode 的重写将放置在未经检查的块中。 - TJ Rockefeller
这很聪明。但如果你决定之后打开它,那就有点糟糕了。 - Hans Passant
@HansPassant 如果它总是在未经检查的块中,那么对于这种情况会更好。 - TJ Rockefeller
2个回答

6
默认情况下,C#项目不会检查溢出和下溢。
右键单击项目,选择属性,在底部的生成选项卡上选择高级...,勾选标记为检查算术溢出/下溢的框。
现在,默认行为是如果有任何溢出而不在显式的unchecked块中,则抛出System.OverflowException
如果您使用项目中打开了溢出检查,自动生成EqualsGetHashCode的覆盖,并且期望存在未经检查的块。
public override int GetHashCode()
{
    unchecked
    {
        var hashCode = -504981047;
        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Foo);
        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Bar);
        return hashCode;
    }
}

2
显然,我对不进行检查、未检查和已检查的理解是有缺陷的。编写一些简单的测试以查看此 fiddle 中的溢出行为非常简单。
简要总结如下:
如果没有明确的检查:
- 如果编译器可以轻松且静态地确定代码将溢出,则会出现编译错误。 - 如果在运行时发生溢出,将不会抛出任何溢出异常。
如果显式设置为未检查:
- 编译器将允许明显会溢出的代码。 - 不会抛出运行时溢出异常。
如果显式设置为已检查:
- 如果编译器可以轻松且静态地确定代码将溢出,则会出现编译错误。 - 如果在运行时发生溢出,将抛出 System.OverflowException 异常。
所以...我想从这一切中得到的教训是,如果你有一些可能会溢出的计算,并且你关心溢出,那么把它放在checked块中非常重要。如果你的代码可能会溢出,而你不关心溢出,显然你可以跳过未经检查的块(除非你的代码从静态分析的角度显然会溢出)。
为了纪念,这里也复制了来自小样的代码。
using System;

public class Program
{
    public static void Main()
    {
        var rand = new Random();
        int test = 0;

        //obscured enough that the compiler doesn't "know" that the line will produce an overflow
        //Does not run explicitly as checked, so no runtime OverflowException is thrown
        test = rand.Next(Int32.MaxValue-2, Int32.MaxValue) + 10;

        //simple enough that the compiler "knows" that the line will produce an overflow
        //Compilation error (line 16, col 10): The operation overflows at compile time in checked mode
        //test = Int32.MaxValue + 1;

        //Explicitly running as unchecked. Compiler allows line that is "known" to overflow.
        unchecked
        {
            test = Int32.MaxValue + 1;
        }

        Console.WriteLine(test);

        //Explicitly running as unchecked. Still no runtime OverflowException
        unchecked
        {
            test = test - 10;   
        }

        Console.WriteLine(test);

        //Explicitly running as checked. System.OverflowException: Arithmetic operation resulted in an overflow.
        checked
        {
            test = test + 10;
        }

        Console.WriteLine(test);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接