2011-06-01 93 views
28

今天我偶然發現了一個我寫的有趣的bug。我有一套屬性可以通過一個普通的setter來設置。這些屬性可以是值類型或引用類型。比較裝箱值類型

public void SetValue(TEnum property, object value) 
{ 
    if (_properties[ property ] != value) 
    { 
     // Only come here when the new value is different. 
    } 
} 

當爲這個方法編寫一個單元測試時,我發現條件對於值類型總是成立的。我花了很長時間才弄清楚這是由於boxing/unboxing。它並沒有花我很長時間來調整代碼如下:

public void SetValue(TEnum property, object value) 
{ 
    if (!_properties[ property ].Equals(value)) 
    { 
     // Only come here when the new value is different. 
    } 
} 

事情是我對這個解決方案並不完全滿意。我想保留一個簡單的參考比較,除非值是裝箱的。

我想到的目前的解決方案只是調用Equals()裝箱值。做a check for a boxed values似乎有點矯枉過正。沒有更簡單的方法嗎?

+0

當然,如果你想不同行爲的盒裝值,那麼你將需要檢查你是否正在處理盒裝值? – LukeH 2011-06-01 17:19:39

+0

使用類型T對此方法進行泛型重載,其中T:struct – 2011-06-01 17:23:56

+1

@lukas將不起作用,除非存在更多與「T」和約束不同的區別。 – 2011-06-01 17:29:43

回答

15

如果你需要,當你有一個價值型交易那麼你顯然會需要進行某些測試的不同的行爲。您不需要明確檢查盒裝的值類型,因爲所有值類型都將被裝箱**,因爲參數被鍵入爲object

此代碼應符合您聲明的標準:如果value是(盒裝)值類型,則調用多態Equals方法,否則使用==來測試引用相等性。

public void SetValue(TEnum property, object value) 
{ 
    bool equal = ((value != null) && value.GetType().IsValueType) 
        ? value.Equals(_properties[property]) 
        : (value == _properties[property]); 

    if (!equal) 
    { 
     // Only come here when the new value is different. 
    } 
} 

(**而且,是的,我知道,Nullable<T>是值類型與有關裝箱和拆箱其自身的特殊規律,但是這幾乎是這裏無關緊要。)

+0

謝謝,這似乎工作完美,並沒有大的開銷。我發現平均運行時間沒有增加。 – 2011-06-02 20:51:04

+0

開銷是在拳擊也調用getType()有成本。最好避免通過生成getter和setter委託來避免它。將事物保持爲實際類型永不裝箱。 – 2017-11-03 17:01:43

1

由於輸入參數的類型爲object,因此您將始終在方法的上下文中獲取裝箱值。

我認爲你唯一的機會是改變方法的簽名並寫出不同的重載。

+0

謝謝,明天會再來一次。 – 2011-06-01 18:12:47

1

如何:

if(object.ReferenceEquals(first, second)) { return; } 
if(first.Equals(second)) { return; } 

// they must differ, right? 

更新

我意識到人們所期待的某些情況下,這並不工作:

  • 對於值類型,ReferenceEquals返回false所以我們回到Equals,其行爲如預期。
  • 對於ReferenceEquals返回true的參考類型,我們認爲它們與預期相同。
  • 對於ReferenceEquals返回false且Equals返回false的參考類型,我們認爲它們與預期的「不同」。
  • 對於那些ReferenceEquals返回false和Equals返回true,我們認爲他們「同」即使我們想要的「不同」

,該課程是「不弄巧」

+0

如果該值剛剛裝箱,如在值類型的情況下,第一個「if」將總是產生錯誤,因此它不會解決OP問題。 – Simone 2011-06-01 17:34:18

+1

這個問題,據我瞭解,「我想保留一個簡單的參考比較,除非價值被裝箱。」這將做到這一點。但是,正如肖恩的回答所言,「如果有人在類中重寫了.Equals(),那是因爲他們想要改變該類型的平等語義,如果沒有令人信服的理由不會」 。我認爲提問者有一個令人信服的理由。 – 2011-06-01 17:36:10

+0

這是一個正確的假設。 ; p – 2011-06-01 18:05:28

10

等於給定類型()通常是首選方法。

.Equals()的默認實現爲參考類型做了一個簡單的參考比較,所以在大多數情況下,這就是你會得到的。 Equals()可能已被覆蓋以提供其他一些行爲,但是如果有人在類中重寫了.Equals(),那是因爲他們想要改變該類型的平等語義,最好讓它發生,如果你不有一個令人信服的理由不會。使用==旁路它會導致混淆,當你的班級看到兩件事情不同時,每個班級都認爲他們是一樣的。

+0

問題恰恰在於'Equals'可能已被覆蓋。儘管兩個對象相同,但這並不意味着一個新的對象(帶有不同的引用)沒有設置。 – 2011-06-01 18:03:40

0

我想

我想保持一個簡單的參考比較,除非值被裝箱。

是有點相當於

如果值是盒裝的,我會做一個非「簡單引用比較」。

這意味着您需要做的第一件事是檢查值是否被裝箱。

如果存在一種方法來檢查一個對象是否是盒裝值類型,它應該至少與您提供鏈接的「過度殺傷」方法一樣複雜,除非這不是最簡單的方法。儘管如此,應該有一個「最簡單的方法」來確定一個對象是否是盒裝值類型。這個「最簡單的方法」不太可能比簡單地使用Equals()方法更簡單,但我已經爲這個問題添加了書籤,以便以防萬一。

(不知道我是合乎邏輯的)