Effective C# Item9:理解幾個相等判斷之間的關係
C#中,有四種方式可以應用於“相等判斷”,如下。
程式碼
public static bool ReferenceEquals( object left, object right );
public static bool Equals( object left, object right );
public virtual bool Equals( object right);
public static bool perator==( MyClass left, MyClass right );
對於一個判斷是否相等的操作,為什麼會有四種形式呢,究其原因,還要看C#的資料型別,C#的資料型別分為值型別和引用型別,其中值型別直接儲存在堆疊上,而引用型別的值儲存在堆上,在棧中保留一個指向堆中地址的引用。這樣在判斷相等的時候,就會產生兩種判斷方式:1)判斷變數在堆疊上儲存的值是否相等,這對於值型別來說,就足夠了,但是對於引用型別來說,只是判斷了堆疊中儲存的引用是否相等,還是不全面的;2)判斷變數在堆中儲存的值是否相等,這主要用於引用型別。
關於如何判斷“相等”,如果兩個引用型別的變數指向同一個物件,那麼它們將被認為是“引用相等”;如果兩個值型別的變數型別相同,而且包含同樣的內容,它們被認為是“值相等”。
對於上述提供的四種用於判斷“相等”的方法,其中前兩種都是Object類帶有的靜態方法,其中Object.ReferenceEquals()方法用於判斷兩個變數的物件標識是否相同,不論是值型別還是引用型別,都是判斷是否“引用相等”,而不是“值相等”,這意味著如果我們對於兩個值型別使用該方法,那麼總是會返回false。
上述描述的第二種方式,是Object型別的靜態方法Equals(),當我們不知道兩個變數的執行時型別時,可以使用這個方法來判斷兩個變數是否相等,由於剛方法並不知道變數的型別,因此,“相等判斷”的操作是依賴於型別的,即它會呼叫其中一個物件例項的Equals方法。靜態 Object.Equals()方法的實現如下。
程式碼
1 public static bool Equals( object left, object right )
2 {
3 // Check object identity
4 if (left == right )
5 return true;
6 // both null references handled above
7 if ((left == null) || (right == null))
8 return false;
9 return left.Equals (right);
10 }
上述第三種方式,是物件例項的Equals()方法,其中System.Object類作為所有類的基類,本身也定義了Equals()方法,Object例項中的Equals()方法,是判斷“引用相等”,其行為和ReferenceEquals()方式完全一樣。而 System.ValueType作為所有值型別的基類,它重寫了Equals()方法,在重寫方法中,是按照“值相等”的方式來進行判斷的,但是,ValueType重寫的Equals()方法效率不高,原因是它使用了反射來得到物件的所有屬性,進而判斷屬性的值是否相同,這樣會導致效能很差,因此,當我們定義一個值型別時,應該總是重寫Equals()方法。
一般,我們重寫Equals()方法的形式如下。
程式碼
1 public class Foo
2 {
3 public override bool Equals( object right )
4 {
5 // check null:
6 // the this pointer is never null in C# methods.
7 if (right == null)
8 return false;
9
10 if (object.ReferenceEquals( this, right ))
11 return true;
12
13 // Discussed below.
14 if (this.GetType() != right.GetType())
15 return false;
16
17 // Compare this type's contents here:
18 return CompareFooMembers(
19 this, right as Foo );
20 }
21 }
22
23
我們在重寫Equals()方法時,應該遵循以下三個原則:
1. 自反性,即a=a
2. 交換性,即如果a=b,那麼b=a
3. 傳遞性,即如果a=b,b=c,那麼a=c
當我們在一個有類繼承層次關係的結構中,為父類和子類都重寫Equals()方法, 那麼很可能造成非常詭異的Bug,我們來看下面的程式碼。
程式碼
1 public class B
2 {
3 public override bool Equals( object right )
4 {
5 // check null:
6 if (right == null)
7 return false;
8
9 // Check reference equality:
10 if (object.ReferenceEquals( this, right ))
11 return true;
12
13 // Problems here, discussed below.
14 B rightAsB = right as B;
15 if (rightAsB == null)
16 return false;
17
18 return CompareBMembers( this, rightAsB );
19 }
20 }
21
22 public class D : B
23 {
24 // etc.
25 public override bool Equals( object right )
26 {
27 // check null:
28 if (right == null)
29 return false;
30
31 if (object.ReferenceEquals( this, right ))
32 return true;
33
34 // Problems here.
35 D rightAsD = right as D;
36 if (rightAsD == null)
37 return false;
38
39 if (base.Equals( rightAsD ) == false)
40 return false;
41
42 return CompareDMembers( this, rightAsD );
43 }
44
45 }
46
47 //Test:
48 B baseObject = new B();
49 D derivedObject = new D();
50
51 // Comparison 1.
52 if (baseObject.Equals(derivedObject))
53 Console.WriteLine( "Equals" );
54 else
55 Console.WriteLine( "Not Equal" );
56
57 // Comparison 2.
58 if (derivedObject.Equals(baseObject))
59 Console.WriteLine( "Equals" );
60 else
61 Console.WriteLine( "Not Equal" );
62
63
如果你認為上述程式碼應該返回兩個“Equals”或者兩個“Not Equals”,那麼無可厚非,但實際上,對於上述的兩次比較,第二次總是會返回false,而第一次有時會返回true,有時會返回false。原因在於型別轉換,子型別是可以預設轉換為父型別的,但是父型別不可以轉換為子型別。
因此,當我們重寫Equals()方法時,有一個很好的建議:如果基類的Equals()方法不是由System.Object或者System.ValueType提供的話,那麼我們也應該在重寫子類的Equals()方法時,呼叫基類的Equals()方法。
關於上述判斷“相等”的方式,總結如下:
1. 永遠不要重寫Object類的ReferenceEquals()和Equals()兩個靜態方法。
2. 對於值型別來說,為了提高效率,我們應該總是重寫例項的Equals()方法和==()操作符,對於引用型別,如果我們認為相等的含義並非是物件標識相同的話,那麼也需要重寫Equals()方法,但是不應該重寫==()操作符,.NET建議所有引用型別上應用==操作時,都遵循“引用相等”。
作者:李勝攀
出處:http://wing011203.cnblogs.com/
程式碼
public static bool ReferenceEquals( object left, object right );
public static bool Equals( object left, object right );
public virtual bool Equals( object right);
public static bool perator==( MyClass left, MyClass right );
對於一個判斷是否相等的操作,為什麼會有四種形式呢,究其原因,還要看C#的資料型別,C#的資料型別分為值型別和引用型別,其中值型別直接儲存在堆疊上,而引用型別的值儲存在堆上,在棧中保留一個指向堆中地址的引用。這樣在判斷相等的時候,就會產生兩種判斷方式:1)判斷變數在堆疊上儲存的值是否相等,這對於值型別來說,就足夠了,但是對於引用型別來說,只是判斷了堆疊中儲存的引用是否相等,還是不全面的;2)判斷變數在堆中儲存的值是否相等,這主要用於引用型別。
關於如何判斷“相等”,如果兩個引用型別的變數指向同一個物件,那麼它們將被認為是“引用相等”;如果兩個值型別的變數型別相同,而且包含同樣的內容,它們被認為是“值相等”。
對於上述提供的四種用於判斷“相等”的方法,其中前兩種都是Object類帶有的靜態方法,其中Object.ReferenceEquals()方法用於判斷兩個變數的物件標識是否相同,不論是值型別還是引用型別,都是判斷是否“引用相等”,而不是“值相等”,這意味著如果我們對於兩個值型別使用該方法,那麼總是會返回false。
上述描述的第二種方式,是Object型別的靜態方法Equals(),當我們不知道兩個變數的執行時型別時,可以使用這個方法來判斷兩個變數是否相等,由於剛方法並不知道變數的型別,因此,“相等判斷”的操作是依賴於型別的,即它會呼叫其中一個物件例項的Equals方法。靜態 Object.Equals()方法的實現如下。
程式碼
1 public static bool Equals( object left, object right )
2 {
3 // Check object identity
4 if (left == right )
5 return true;
6 // both null references handled above
7 if ((left == null) || (right == null))
8 return false;
9 return left.Equals (right);
10 }
上述第三種方式,是物件例項的Equals()方法,其中System.Object類作為所有類的基類,本身也定義了Equals()方法,Object例項中的Equals()方法,是判斷“引用相等”,其行為和ReferenceEquals()方式完全一樣。而 System.ValueType作為所有值型別的基類,它重寫了Equals()方法,在重寫方法中,是按照“值相等”的方式來進行判斷的,但是,ValueType重寫的Equals()方法效率不高,原因是它使用了反射來得到物件的所有屬性,進而判斷屬性的值是否相同,這樣會導致效能很差,因此,當我們定義一個值型別時,應該總是重寫Equals()方法。
一般,我們重寫Equals()方法的形式如下。
程式碼
1 public class Foo
2 {
3 public override bool Equals( object right )
4 {
5 // check null:
6 // the this pointer is never null in C# methods.
7 if (right == null)
8 return false;
9
10 if (object.ReferenceEquals( this, right ))
11 return true;
12
13 // Discussed below.
14 if (this.GetType() != right.GetType())
15 return false;
16
17 // Compare this type's contents here:
18 return CompareFooMembers(
19 this, right as Foo );
20 }
21 }
22
23
我們在重寫Equals()方法時,應該遵循以下三個原則:
1. 自反性,即a=a
2. 交換性,即如果a=b,那麼b=a
3. 傳遞性,即如果a=b,b=c,那麼a=c
當我們在一個有類繼承層次關係的結構中,為父類和子類都重寫Equals()方法, 那麼很可能造成非常詭異的Bug,我們來看下面的程式碼。
程式碼
1 public class B
2 {
3 public override bool Equals( object right )
4 {
5 // check null:
6 if (right == null)
7 return false;
8
9 // Check reference equality:
10 if (object.ReferenceEquals( this, right ))
11 return true;
12
13 // Problems here, discussed below.
14 B rightAsB = right as B;
15 if (rightAsB == null)
16 return false;
17
18 return CompareBMembers( this, rightAsB );
19 }
20 }
21
22 public class D : B
23 {
24 // etc.
25 public override bool Equals( object right )
26 {
27 // check null:
28 if (right == null)
29 return false;
30
31 if (object.ReferenceEquals( this, right ))
32 return true;
33
34 // Problems here.
35 D rightAsD = right as D;
36 if (rightAsD == null)
37 return false;
38
39 if (base.Equals( rightAsD ) == false)
40 return false;
41
42 return CompareDMembers( this, rightAsD );
43 }
44
45 }
46
47 //Test:
48 B baseObject = new B();
49 D derivedObject = new D();
50
51 // Comparison 1.
52 if (baseObject.Equals(derivedObject))
53 Console.WriteLine( "Equals" );
54 else
55 Console.WriteLine( "Not Equal" );
56
57 // Comparison 2.
58 if (derivedObject.Equals(baseObject))
59 Console.WriteLine( "Equals" );
60 else
61 Console.WriteLine( "Not Equal" );
62
63
如果你認為上述程式碼應該返回兩個“Equals”或者兩個“Not Equals”,那麼無可厚非,但實際上,對於上述的兩次比較,第二次總是會返回false,而第一次有時會返回true,有時會返回false。原因在於型別轉換,子型別是可以預設轉換為父型別的,但是父型別不可以轉換為子型別。
因此,當我們重寫Equals()方法時,有一個很好的建議:如果基類的Equals()方法不是由System.Object或者System.ValueType提供的話,那麼我們也應該在重寫子類的Equals()方法時,呼叫基類的Equals()方法。
關於上述判斷“相等”的方式,總結如下:
1. 永遠不要重寫Object類的ReferenceEquals()和Equals()兩個靜態方法。
2. 對於值型別來說,為了提高效率,我們應該總是重寫例項的Equals()方法和==()操作符,對於引用型別,如果我們認為相等的含義並非是物件標識相同的話,那麼也需要重寫Equals()方法,但是不應該重寫==()操作符,.NET建議所有引用型別上應用==操作時,都遵循“引用相等”。
作者:李勝攀
出處:http://wing011203.cnblogs.com/
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-624812/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- golang中判斷兩個slice是否相等與判斷值下的 陣列是否相等Golang陣列
- js判斷兩個物件是否相等JS物件
- AutoCAD C# 判斷多邊形與點的位置關係C#
- JavaScript判斷兩個變數是否相等JavaScript變數
- 多個陣列,判斷從屬關係陣列
- python如何判斷字串相等Python字串
- JavaScript 中,如何判斷兩個物件是否相等?JavaScript物件
- 判斷陣列裡面的json 物件是否相等,相等則移除陣列JSON物件
- 理解 virt、res、shr 之間的關係(linux 系統篇)Linux
- 理解virt、res、shr之間的關係(linux系統篇)Linux
- 多個陣列,判斷鍵值是否存在從屬關係陣列
- 如何判斷JavaScript中的兩變數是否相等?JavaScript變數
- 程式碼段——C#判斷時間是否在某個範圍C#
- 【java】類之間的關係Java
- 【重溫基礎】18.相等性判斷
- 頁面中多個script塊之間的關係
- if、else if、else判斷語句的幾個小例子
- Window、WindowManager、View 之間的關係View
- git、github、gitlab之間的關係GithubGitlab
- LC49判斷二叉樹是否相等二叉樹
- abc250E 判斷字首構成的集合是否相等
- 效能測試各個指標之間關係指標
- 瞭解下C# 判斷C#
- 【資料庫】關係模式的正規化的簡明判斷資料庫模式
- TLS與SSL之間關係TLS
- 類與類之間的基本關係
- 思考 TPS 與 RT 之間的關係
- Window, WindowManager和WindowManagerService之間的關係
- React、Ant Design、DvaJS之間的關係ReactJS
- Activity、View、Window之間關係的分析View
- UML類圖--類之間的關係
- .Net中字串不變性與相等判斷的特殊場景字串
- .NET科普:.NET簡史、.NET Standard以及C#和.NET Framework之間的關係C#Framework
- 通俗地理解面向服務的架構(SOA)以及微服務之間的關係架構微服務
- js判斷物件的幾種方法JS物件
- lisp 判斷時間Lisp
- js判斷時間JS
- 請描述下js的原型和原型鏈的理解以及它們之間的關係JS原型
- 類之間的6種關係詳解