国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

C#相等性比較

2019-11-17 03:19:02
字體:
來源:轉載
供稿:網友

C#相等性比較

本文闡述C#中相等性比較,其中主要集中在下面兩個方面

==和!=運算符,什么時候它們可以用于相等性比較,什么時候它們不適用,如果不使用,那么它們的替代方式是什么?

什么時候,需要自定一個類型的相等性比較邏輯

在闡述相等性比較,以及如何自定義相等性比較邏輯之前,我們首先了解一下值類型比較和引用類型比較

值類型比較對比引用類型比較

C#中的相等性比較有兩種:

  • 值類型相等,兩個值在某種場景下相等
  • 引用類型相等,兩個引用指向同一個對象

默認情況下,

  • 值類型使用值類型相等
  • 引用類型使用引用相等

實際上,值類型只能使用值相等(除非值類型進行了裝箱操作)。看一個簡單的例子(比較兩個數字),運行的結果為True

int x = 5, y = 5;Console.WriteLine(x == y);

默認地,引用類型使用引用相等。比如下面的例子:返回False

object x = 5, y = 5;Console.WriteLine(x == y);

如果x和y指向同一個對象,那么將返回True:

object x = 5, y = x;Console.WriteLine(x == y);

相等性的標準

下面三個標準用于實現相等性比較:

  • ==和!=運算符
  • object中的虛方法Equals
  • IEquatable<T>接口

下面我們來分別闡述

1. ==和!=運算符

使用==和!=的原因是它們是運算符,它們通過靜態函數實現相等性比較。因此,當你使用==或!=時,C#在編譯時就決定了所比較的類型,而且不會執行任何虛方法(Object.Equals)。這是大家所期望的相等行比較。比如在第一個數字比較的例子中,編譯器在編譯時就決定了執行==運算的類型是int類型,因為x和y都是int類型。

而第二個例子,編譯器決定執行==運算的類型是object類型,因為object是類(引用類型),因此對象的==運算符采取引用相等去比較x和y。那么結果就返回False,這是因為x和y指向堆上不同的對象(被裝箱的int)

2. Object.Equals虛方法

為了正確地比較第二個例子中的x和y,我們可以使用Equals虛方法。System.Object中定義了Equals虛方法,它適用于所有類型

object x = 5, y = 5;Console.WriteLine(x.Equals(y));

Equals在程序運行時決定比較的類型--根據對象的實際類型進行比較。在上面的例子中,會調用Int32的Euqals方法,該方法使用值相等進行比較,所以上面的例子返回True。如果x和y是引用類型,那么調用引用相等進行比較;如果x和y是結構類型,那么Equals會調用結構每個成員對應類型的Equals方法進行比較。

看到這里,你可能會想,為什么C#的設計者不把==設計成virtaul,從而使其與Equals一樣,以避免上訴缺陷。這是因為:

  • 如果第一個運算對象是null,Equals方法會拋出NullReferenceException異常;而靜態的運算符則不會
  • 因為==運算符在編譯時決定了比較類型(靜態解析比較類型),那么它的執行就非常快。這也就使得編寫大量運算代碼去執行相等性比較時對性能不會帶來太大的影響
  • 有時候,==和Equals適用于不同的場景的相等性比較。(后續的內容會涉及)

簡而言之,復雜的設計反映了復雜的場景:相等的概念涉及到許多場景。

而Euqals方法,適用于比較兩個未知類型的對象,下面的這個方法就適用于比較任何類型的兩個對象:

public static bool AreEqual(object obj1, object obj2){    return obj1.Equals(obj2);}

但是,該函數不能處理第一個參數是null的情形,如果第一個函數是null,你會得到NullReferenceException異常。因此我們需要對該函數進行修改:

public static bool AreEqual(object obj1, object obj2){    if (obj1 == null)        return obj2 == null;    return obj1.Equals(obj2);}

object的靜態Equals方法

object類還定義了一個靜態Equals方法,它的作用與AreEquals方法一樣。

public static bool Equals(Object objA, Object objB){    if (objA==objB) {        return true;    }    if (objA==null || objB==null) {        return false;    }    return objA.Equals(objB);}

這樣就可以對編譯時不知道類型的null對象進行安全地比較。

object x = 5, y = 5;Console.WriteLine(object.Equals(x, y)); // -> Truex = null;Console.WriteLine(object.Equals(x, y)); // -> Falsey = null;Console.WriteLine(object.Equals(x, y)); // -> TrueConsole.WriteLine(x.Equals(y)); // -> NullReferebceException, because x is null

請注意,當編寫Generic類型時,下面的代碼將不能通過編譯(除非把==或!=運算符替換成Object.Equals方法的調用):

public class Test<T> : IEqualityComparer<T>{    T _value;    public void SetValue(T newValue)    {        // Operator '!=' cannot be applied to operands of type 'T' and 'T'        // it should be : if(!object.Equals(newValue, _value))        if (newValue != _value)            _value = newValue;    }}

object的靜態ReferenceEquals方法

有時候,你需要強行比較兩個引用是否相等。這個時候,你就需要使用object.ReferenceEquals:

internal class Widget {    public string UID { get; set; }    public override bool Equals(object obj)    {        if (obj == null)            return this == null;        if (!(obj is Widget))            return false;        Widget w = obj as Widget;        return this.UID == w.UID;    }    public override int GetHashCode()            {                return this.UID.GetHashCode();            }    public static bool operator  == (Widget w1, Widget w2)            {                return w1.Equals(w2);            }    public static bool operator !=(Widget w1, Widget w2)            {                return !w1.Equals(w2);            }}static void Main(string[] args){    Widget w1 = new Widget();    Widget w2 = new Widget();    Console.WriteLine(w1==w2); // -> True    Console.WriteLine(w1.Equals(w2)); // -> True    Console.WriteLine(object.ReferenceEquals(w1, w2)); // -> False     Console.ReadLine();}
Basic ReferenceEquals

之所以調用ReferenceEquals方法,這是因為自定義類Widget重寫了object類的虛方法Equals;此外,該類還重寫了操作符==和!=,因此執行==時操作也返回True。所以,調用ReferenceEquals可以確保返回引用是否相等。

3. IEquatable<T>接口

調用object.Equals方法實際上對進行比較的值類型進行了裝箱操作。在對性能有較高要求的場景,那么就不適合使用這種方式。從C#2.0開始,通過引入IEquatable<T>接口來解決這個問題

public interface IEquatable<T>{    bool Equals(T other);}

當實現IEquatable接口之口,調用接口方法就等同于調用objet的虛方法Equals,但是接口方法執行地更快(不需要類型轉換)。大多數.NET基本類型都實現了IEquatable<T>接口,你還可以為Generic類型添加IEquatable<T>限制

internal class Test<T> where T : IEquatable<T>{    public bool IsEqual(T t1, T t2)    {        return t1.Equals(t2);    }}

如果,我們移除IEquatable<T>限制,Test<T>類仍可以通過編譯,但是t1.Equals(t2)將使用object.Equals方法。

4. 當Equals結果與==的結果不一致

在前面的內容中,我們已經提到有時候,==或equals適用于不同的場景。比如:

double x = double.NaN;Console.WriteLine(x == x); // FalseConsole.WriteLine(x.Equals(x)); // True

這是因為double類型的==運算符強制NaN不等于其他任何值,即使另外一個NaN。從數學的角度來講,兩個確實不相等。而Equals方法,因為具有對稱性,所以x.Equals(x)總返回True。

集合與字典正是依賴于Equals的對稱性,否則就不能找到已經保存在集合或字典中的元素。

對于值類型而言,Equals和==很少出現不同的相等性。而在引用類型中,則比較常見。一般地,引用類型的創建者重寫Equals方法執行值相等比較,而保留==執行引用相等比較。比如StringBuilder類就是這樣的:

StringBuilder buffer1 = new StringBuilder("123");StringBuilder buffer2 = new StringBuilder("123");Console.WriteLine(buffer1 == buffer2); // FalseConsole.WriteLine(buffer1.Equals(buffer2)); // True

比較自定義類型

回顧一下默認的比較行為

  • 值類型使用值相等
  • 引用類型使用引用相等

進一步,

  • 結構類型的equals方法會根據每個字段的類型進行相等行比較

有時候,在創建類型時,需要重寫上述行為,一般在下面兩種情形下需要重寫:

  • 更改相等的意義
  • 提高結構類型的比較速度

1)更改相等的意義

當默認的==和Equals不適用(不符合自然規則,或悖離了使用者的期望)于自定義類型時,就需要更改相等的意義。比如DateTimeOffset結構,其有兩個私有成員:一個DateTime類型的UTC,以及int類型的offset。如果是你在創建DateTimeOffset類型,那么你很可能只要UTC字段相等即可,而不去比較Offset字段。另外一個例子就是支持NaN的數字類型,比如float和double,如果你來創建這兩個類型,你可能會希望NaN也是可以進行比較的。

而對于Class類型,很多時候,使用值比較更有意義。尤其是一些包含較少數據的類,比如System.Uri或System.String

2)提高結構類型的比較速度

結構類型的默認比較算法相對較慢。通過重寫Equals方法可以提高5%的性能。而重載==運算和實現IEquatable<T>可以在不裝箱操作的情況下實現相等性比較,這使得提高5%性能變得可能。

對于自定義相等比較,有一個特殊的情形,更改結構類型的hashing算法后,hashtable可以獲得更好的性能。這是因為hashing算法和相等性比較都發生在棧上。

3)如何重寫相等

總地來說,有下面三種方式:

  • 重寫GetHashcode()和Equals()
  • 【可選】重載!=和==
  • 【可選】實現IEquatable<T>

I)重寫GetHashCode

object對象的虛方法GetHashCode,也就僅僅對于Hashtable類型和Dictionary<TKey,TValue>類型有益。

這兩個類型都是哈希表集合,集合中的每個元素都是一個鍵值用于存儲元素和獲取元素。哈希表使用了一個特定的策略以有效地基于元素的鍵值分配元素。這就要求每個鍵值都有一個Int32數(或哈希碼)。哈希碼不僅對于每個鍵值是唯一的,而且還必須有較好的性能。哈希表認為object類定義的GetHashCode方法已經足夠了,因此這兩個類型都省略了獲取哈希碼的方法。

無論值類型還是引用類型,都默認實現了GetHashCode方法,所以你不用重寫這個方法,除非你需要重寫Equals方法。(因此,如果你重寫了GetHashCode方法,那么你肯定是需要重寫Equals方法)。

是否需要重寫GetHashCode方法,可以參考下面的規則:

  • 如果Equals方法返回True是,兩個比較的對象必須返回相同的哈希碼
  • 不允許拋出異常
  • 除非對象變化了,那么重復的對一個對象調用GetHashCode方法應返回相同的哈希碼

為了提高哈希表的性能,GetHashCode需要重寫以防止不同的值返回相同的哈希碼。也這就說明了為什么需要對結構類型需要重寫Equals和GetHashCode方法,因此這樣重寫比默認的哈希算法更有效率。結構類型的GetHashCode方法的默認實現是在運行時才發生,而且很可能基于結構的每個成員而實現。

// char typepublic override int GetHashCode() {    return (int)m_value | ((int)m_value << 16);}// int32public override int GetHashCode() {    return m_value;}

而類(class)類型,GetHashCode方法的默認實現基于內部對象標識,這個標識在CLR中對于每個對象實例都是唯一的。

public virtual int GetHashCode(){    return RuntimeHelpers.GetHashCode(this);}

II)重寫Equals

object.Equal的規定(公理)如下:

  • 一個對象不能和null相等(除非對象是nullable類型)
  • 相等性是對稱的(一個對象等于自身)
  • 相等性是可交換的(如果a等于b,那么b也等于a)
  • 相等性是可傳
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 铜陵市| 张家港市| 闻喜县| 清新县| 南木林县| 紫金县| 龙游县| 四会市| 花莲市| 文安县| 南昌县| 边坝县| 五峰| 宁陕县| 凌云县| 连山| 石林| 电白县| 通道| 镶黄旗| 桦川县| 华亭县| 迁西县| 诸城市| 和平县| 汕头市| 华宁县| 尖扎县| 龙山县| 敦煌市| 新丰县| 老河口市| 广元市| 通山县| 农安县| 浪卡子县| 衡水市| 汉沽区| 湖州市| 邛崃市| 柳州市|