2010-03-30 53 views
49

我查找了結構的重寫指導原則,但是我可以找到的所有指南都是針對類的。構造中的重寫等式方法

起初我以爲我不需要檢查傳遞的對象是否爲null,因爲結構是值類型並且不能爲null。但現在,我想起來了,因爲等於簽名是

public bool Equals(object obj) 

似乎沒有受到什麼阻礙我的結構的用戶試圖將它與任意的引用類型進行比較。

我的第二點涉及到我在我的結構中比較我的私有字段之前,我想(我想)必須做的。我該如何將對象轉換爲我的結構類型? C#的as關鍵字似乎只適用於引用類型。

+6

只是說明你鼓勵避免.Net中的可變結構。它的設置應該堅持引用類型(類)大部分時間,並且很少使用結構。 – 2010-03-30 03:34:40

+4

我第二。使用不可變結構*不帶*子類型。然後Equals和==對於給定的接收者(左側值)應該是相同的,其中實現中唯一的區別是Equals需要'is'檢查,然後爲簡單起見,將調度爲==。因此,兩個合同都得到履行,意外情況得到緩解。 – 2010-03-30 03:40:56

+0

是的,這個結構是不可變的。我只比較一個int。 – 2010-03-30 03:51:00

回答

64
struct MyStruct 
{ 
    public override bool Equals(object obj) 
    { 
     if (!(obj is MyStruct)) 
      return false; 

     MyStruct mys = (MyStruct) obj; 
     // compare elements here 

    } 

} 
+1

爲了清楚起見,你可以在這裏添加'override'關鍵字嗎?在Java中總是很好的做法,在C#中應該是一樣的。 – 2013-08-06 13:12:13

+1

另請參閱Microsoft的指導原則 - http://msdn.microsoft.com/en-us/library/ms173147(v=vs.80).aspx – yoyo 2014-08-26 17:23:20

+7

@JohanS在C#中,不僅僅是一種好的做法,如果您省略了重寫該方法實際上做了完全不同的事情。 – Pharap 2014-12-23 09:08:19

5

使用is操作:

public bool Equals(object obj) 
{ 
    if (obj is MyStruct) 
    { 
    var o = (MyStruct)obj; 
    ... 
    } 
} 
0

添加到現有的答案。

如果你添加一個?你仍然可以有空值。該結構名稱後(這個工程的每一個值對象)

int? 

鑄造是通過調用(MyStructName)variableName

+2

你可以,但是可能有一個非常高的性能損失,會比通過使用「as」而不是「is」而獲得的利益多得多。 – 2010-03-30 16:46:52

+0

@DanStory我不會那麼快。如果你想看看[this](http://stackoverflow.com/a/28281410),我很想知道是否有任何我錯過了。 tl; dr:is + cast的確編譯得更好一些,但是對於as + boxing來說,似乎並沒有像「非常高的性能損失」那樣的東西。事實上,我不能讓is + cast的運行速度更快(有時候as + boxing方法會帶頭)。 – tne 2015-02-02 15:49:53

+0

@DanStory對於之前的評論,我絕對是錯的。懲罰* *高(無論如何,與微基準的選擇相比)。編輯了相同的鏈接答案。 – tne 2015-06-17 16:47:37

12

也做了,我想,如果一個人的使用.NET 4.5,可以使用缺省實現注意在documentation中:

當您定義自己的類型時,該類型會繼承由其基類型的Equals方法定義的功能。

ValueType.Equals:Value equality;可以直接逐字節比較或使用反射進行逐場比較。

+1

它實際上是在4.5之前,我不知道它是什麼時候添加的,但它在4中是絕對可用的。儘管對MSDN的一個評論似乎表明它對於浮點類型可能不準確。 – Pharap 2014-12-23 09:22:02

+1

有關性能考慮,請參閱http://stackoverflow.com/q/1009394。 – tne 2015-02-02 14:53:01

+0

@Pharap:當定義「Equals」時,Microsoft未能清楚地說明,事情應該如何「平等」才能返回「真實」;有一些上下文有用於測試*等價性*的浮點值(正數和負數在數值上相等並不意味着它們是等價的,因爲如果x和y是等價的,那應該意味着1/x = = 1/y,但這不是正值和負值)。對某些結構中的浮點值進行等價測試,但我知道沒有通用的方法來請求這樣的測試。 – supercat 2015-02-03 00:40:01

6

如果任何人的疑惑拳擊在可空對象的結構的性能損失(以避免is double類型檢查和演員),還有一個不可忽略的開銷。

tl; dr:在這種情況下使用is &。

struct Foo : IEquatable<Foo> 
{ 
    public int a, b; 

    public Foo(int a, int b) 
    { 
     this.a = a; 
     this.b = b; 
    } 

    public override bool Equals(object obj) 
    { 
#if BOXING 
     var obj_ = obj as Foo?; 
     return obj_ != null && Equals(obj_.Value); 
#elif DOUBLECHECK 
     return obj is Foo && Equals((Foo)obj); 
#elif MAGIC 
     ? 
#endif 
    } 

    public bool Equals(Foo other) 
    { 
     return a == other.a && b == other.b; 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     RunBenchmark(new Foo(42, 43), new Foo(42, 43)); 
     RunBenchmark(new Foo(42, 43), new Foo(43, 44)); 
    } 

    static void RunBenchmark(object x, object y) 
    { 
     var sw = Stopwatch.StartNew(); 
     for (var i = 0; i < 100000000; i++) x.Equals(y); 
     sw.Stop(); 
     Console.WriteLine(sw.ElapsedMilliseconds); 
    } 
} 

結果:

BOXING 
EQ 8012 7973 7981 8000 
NEQ 7929 7715 7906 7888 

DOUBLECHECK 
EQ 3654 3650 3638 3605 
NEQ 3310 3301 3319 3297 

警告:此測試可能會在許多方面是有缺陷的,雖然我沒有驗證碼基準本身以奇怪的方式未進行優化。

看着IL,雙重檢查方法編譯一點清潔。

拳擊IL:

.method public hidebysig virtual 
    instance bool Equals (
     object obj 
    ) cil managed 
{ 
    // Method begins at RVA 0x2060 
    // Code size 37 (0x25) 
    .maxstack 2 
    .locals init (
     [0] valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo> obj_ 
    ) 

    IL_0000: ldarg.1 
    IL_0001: isinst valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo> 
    IL_0006: unbox.any valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo> 
    IL_000b: stloc.0 
    IL_000c: ldloca.s obj_ 
    IL_000e: call instance bool valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo>::get_HasValue() 
    IL_0013: brfalse.s IL_0023 

    IL_0015: ldarg.0 
    IL_0016: ldloca.s obj_ 
    IL_0018: call instance !0 valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo>::get_Value() 
    IL_001d: call instance bool StructIEqualsImpl.Foo::Equals(valuetype StructIEqualsImpl.Foo) 
    IL_0022: ret 

    IL_0023: ldc.i4.0 
    IL_0024: ret 
} // end of method Foo::Equals 

仔細檢查IL:

.method public hidebysig virtual 
    instance bool Equals (
     object obj 
    ) cil managed 
{ 
    // Method begins at RVA 0x2060 
    // Code size 23 (0x17) 
    .maxstack 8 

    IL_0000: ldarg.1 
    IL_0001: isinst StructIEqualsImpl.Foo 
    IL_0006: brfalse.s IL_0015 

    IL_0008: ldarg.0 
    IL_0009: ldarg.1 
    IL_000a: unbox.any StructIEqualsImpl.Foo 
    IL_000f: call instance bool StructIEqualsImpl.Foo::Equals(valuetype StructIEqualsImpl.Foo) 
    IL_0014: ret 

    IL_0015: ldc.i4.0 
    IL_0016: ret 
} // end of method Foo::Equals 

道具羅馬萊納爲察覺真的沒有讓我看好了一個錯誤。

+0

您的測試* *有瑕疵!你的基準調用'Foo.Equals(Foo)'方法。 'Foo.Equals(object)'永遠不會執行。 – 2015-06-17 06:39:15

+0

@RomanReiner:噢,恥辱。我顯然是想拋棄物體而忘了;帶來很大的後果(實際結果是非常不同的) - 好吧,如果任何人都重視微基準。非常感謝! – tne 2015-06-17 10:26:53

1

由於some news in C# 7.0有來完成同樣作爲公認的答案更簡單的方法:

struct MyStruct 
{ 
    public override bool Equals(object obj) 
    { 
     if (!(obj is MyStruct mys)) // type pattern here 
      return false; 

     return this.field1 == mys.field1 && this.field2 == mys.field2 // mys is already known here without explicit casting 
    } 
} 

還是我最喜歡的 - 一樣的表情濃郁功能:

struct MyStruct 
{ 
    public override bool Equals(object obj) => 
     obj is MyStruct mys 
      ? true // the initial "true" doesn't affect the overall boolean operation yet allows nice line aligning below 
       && this.field1 == mys.field1 
       && this.field2 == mys.field2 
      : false; // obj is not MyStruct 
}