2010-02-05 67 views
11

嗨,我使用LINQ的Enumerable.Sum()擴展方法來計算哈希代碼,並且在代碼變大時遇到OverflowExceptions問題。我試圖將電話撥入unchecked區塊,但這似乎沒有幫助。Enumerable.Sum()溢出

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; 
} 

在此基礎上編譯,我就指望它根據調用代碼的上下文而定溢出或不溢出。它爲什麼會溢出,我怎麼才能讓它停止?

+3

這不是回答有關溢出的問題......但如果你使用Sum來計算一個對象的哈希碼,你可能不會創建分佈很好的哈希碼。典型的方法是在未經檢查的環境中進行乘法運算和左乘法運算。 – 2010-02-05 17:01:17

+0

是的,這並不理想,但是我總結的哈希碼(子組件的哈希碼)是以更好的方式生成的,所以我並不擔心它。 (我不只是加上'int's,小的變化不會產生一個非常不同的代碼。)我認爲這不是我應該瘋了,但也許它比我想象的更重要...? – 2010-02-05 17:32:08

+0

如果哈希碼在Int32.MaxValue處或其附近,您可能只會溢出兩個項目。由於你正在處理整數,所以在你有很多項目之前這個問題並不明顯,但是如果散列函數分佈正確,這將會拋出異常,這往往會導致異常 – thecoop 2010-02-05 17:55:28

回答

9

該代碼確實在C#checked塊中執行。問題是反射器沒有正確反編譯checked塊,而是將它們顯示爲正常的數學運算。您可以通過創建一個檢查的塊,編譯代碼然後反射器反編譯來驗證它。

你也可以通過查看IL而不是反編譯的C#代碼來驗證這一點。而不是添加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 

拋出有沒有辦法讓這個特殊的方法不溢拋出。你最好的選擇是以下

  1. 切換到更大的類型,如long
  2. 自己寫的總和的版本,它不使用檢查除了
+0

謝謝。我必須更加適應IL ... – 2010-02-05 20:04:30

1

checked僅適用於表達當前塊,而不是任何(已經編譯的)被調用的方法。要使用未經檢查的數學,您需要在unchecked塊內實現您自己的Sum版本

+0

因此,選中/未選中的區分是確定的在編譯時?我會期望它是一個運行時,取決於上下文,但我想我會錯。 – 2010-02-05 17:34:16

+0

正如JaredPar所回答的那樣,無論是在一個已選中還是未選中的塊中,它都會生成不同的IL命令;你不能改變已編譯的IL – thecoop 2010-02-05 17:53:26

7

我爲泛型枚舉編寫了此函數。我很樂意聽到關於它的任何評論。

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

我喜歡那樣。據我所見,檢查obj == null缺失。 – Fried 2015-07-29 06:39:30