Перечисляемое переполнение (Sum)

Эй, я использую метод расширения Enumerable.Sum() от LINQ для вычисления хеш-кодов, и у меня проблема с OverflowExceptions, когда код становится большим. Я попытался поместить вызов в блок unchecked, но это не помогло.

Документация MSDN для метода говорит, что он будет бросать, если значение становится слишком большим, но я проверил в рефлекторе, и это все, что есть:

public static int Sum(this IEnumerable<int> source) {
    if (source == null) {
        throw Error.ArgumentNull("source");
    }
    int num = 0;
    foreach (int num2 in source) {
        num += num2;
    }
    return num;
}

Основываясь на этой декомпиляции, я ожидаю, что она будет либо переполняться, либо не зависящей от контекста вызывающего кода. Почему это переполняется, и как я могу заставить его остановиться?

Ответ 1

Код действительно выполняется в блоке С# checked. Проблема в том, что рефлектор неправильно декомпилирует блоки checked и вместо этого отображает их как обычные математические операции. Вы можете проверить это самостоятельно, создав проверенный блок, скомпилировав код и затем декомпилировав его в рефлекторе.

Вы также можете проверить это, посмотрев IL, а не декомпилированный код С#. Вместо кода ввода IL IL вы увидите, что добавление происходит с помощью add.ovf. Это версия добавления, которая бросает на переполнения

L_001a: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
L_001f: stloc.1 
L_0020: ldloc.0 
L_0021: ldloc.1 
L_0022: add.ovf <-- This is an overflow aware addition
L_0023: stloc.0 
L_0024: ldloc.2 

Невозможно заставить этот конкретный метод не перебрасывать. Ваши лучшие варианты следующие

  • Перейдите к большему типу, например long
  • Напишите свою собственную версию Sum, которая не использует проверенное дополнение

Ответ 2

Я написал эту функцию для общих перечислений. Мне бы хотелось услышать какие-либо замечания по этому поводу.

public static int SequenceHashCode<T>(IEnumerable<T> seq)
{
    unchecked
    {
        return seq != null ? seq.Aggregate(0, (sum,obj) => sum+obj.GetHashCode()) : 0;
    }
}

Ответ 3

checked применяется только к выражениям в текущем блоке, а не к любому (уже скомпилированному) вызванному методу. Чтобы использовать непроверенную математику, вам нужно реализовать собственную версию Sum внутри блока unchecked