目錄:
- 建議10:建立物件時需要考慮是否實現比較器
- 建議11:區別對待==與Equals
- 建議12:重寫Equals時也要重寫GetHashCode
一、建議10:建立物件時需要考慮是否實現比較器
比較一下基本工資:
class Salary : IComparable { public string Name { get; set; } public decimal BaseSalary { get; set; }
public decimal Bonus { get; set; } public int CompareTo(object obj) { Salary comparer = obj as Salary; if (BaseSalary > comparer.BaseSalary) { return 1; } else if (BaseSalary == comparer.BaseSalary) { return 0; } else { return -1; } } }
客戶端呼叫:
List<Salary> salaries = new List<Salary>(); salaries.Add(new Salary() { Name = "Sun", BaseSalary = 1000 }); salaries.Add(new Salary() { Name = "Yuan", BaseSalary = 2000 }); salaries.Add(new Salary() { Name = "Kun", BaseSalary = 3000 }); salaries.Add(new Salary() { Name = "Qun", BaseSalary = 3000 }); salaries.Add(new Salary() { Name = "Sun", BaseSalary = 4000 }); salaries.Sort(); foreach (var s in salaries) { Console.WriteLine("【Name】:{0},【BaseSalary】:¥{1}{2}", s.Name, s.BaseSalary,System.Environment.NewLine); } Console.ReadKey();
執行:
如果不想用基本工資BaseSalary進行排序,而是以獎金Bonus進行排序,使用IComparer實現自定義比較器:
class BonusComparer : IComparer<Salary> { public int Compare(Salary x, Sarlary y) { return left.Bonus.CompareTo(right.Bonus); } }
客戶端提供我們上面建立的比較器:
List<Salary> salaries = new List<Salary>(); salaries.Add(new Salary() { Name = "Sun", BaseSalary = 1000,Bonus=4000 }); salaries.Add(new Salary() { Name = "Yuan", BaseSalary = 2000, Bonus = 3000 }); salaries.Add(new Salary() { Name = "Kun", BaseSalary = 3000, Bonus = 2000 }); salaries.Add(new Salary() { Name = "Qun", BaseSalary = 3000,Bonus=4000 }); salaries.Add(new Salary() { Name = "Dun", BaseSalary = 4000,Bonus=0 }); salaries.Sort(new BonusComparer()); foreach (var s in salaries) { Console.WriteLine("Name:【{0}】,BaseSalary:¥{1},Bonus:{2}{3}", s.Name, s.BaseSalary,s.Bonus, System.Environment.NewLine); } Console.ReadKey();
輸出:
二、建議11:區別對待==與Equals
兩者都是指相等性,即:值相等性和引用相等性。
值型別:如果值型別相等,返回True。
引用型別:如果指向同一個引用,返回True。
很好理解,舉個例子:
1、值型別:==與Equls()
int x = 1; int y = 1; Console.WriteLine("int x=1;{0}int y=1;{0}", System.Environment.NewLine,System.Environment.NewLine); Console.WriteLine("x==y:{0}",x == y); Console.WriteLine("x.Equals(y):{0}{1}",x.Equals(y),System.Environment.NewLine); x = y; Console.WriteLine("x=y;{0}", System.Environment.NewLine); Console.WriteLine("x==y:{0}", x == y); Console.WriteLine("x.Equals(y):{0}", x.Equals(y)); Console.ReadKey();
執行:
2、引用型別
class People { public String Name { get; set; } }
客戶端:
People p1 = new People() { Name = "Sun" }; People p2 = new People() { Name = "Yuan" }; Console.WriteLine("People p1 = new People();{0}People p2 = new People();{1}", System.Environment.NewLine, System.Environment.NewLine); Console.WriteLine("p1==p2:{0}", p1 == p2); Console.WriteLine("p1.Equals(p2):{0}{1}", p1.Equals(p2), System.Environment.NewLine); Console.WriteLine("------------------------------------"); p1 = p2; p1.Name = "Moon"; Console.WriteLine("p1=p2;{0}", System.Environment.NewLine); Console.WriteLine("p1==p2:{0}", p1 == p2); Console.WriteLine("p1.Equals(p2):{0}", p1.Equals(p2));
執行:
後面我們修改了p1裡Name="Moon"的值,但是,p2的Name值也變成了Moon。所以,==與Equal()在比較引用型別時,引用地址一樣,返回True
3、引用型別過載Equals()達到值型別比較效果
還有一點,有時我們需要我們的型別看上去和string型別類似,有值型別的感覺。所以說,我們的這個引用型別,需要過載==或者Equals()。
這裡建議只過載Equals()來達到像值型別一樣的比較效果。保留==,保留引用比較。例如:生活中我們認為身份證號碼一樣的是同一個人。
class People { public String Name { get; set; } public string IDCode { get; set; } public override bool Equals(object obj) { People p = obj as People; return p.IDCode == IDCode; } }
客戶端:
People p1 = new People() { IDCode="No1" }; People p2 = new People() { IDCode = "No1" }; Console.WriteLine("People p1 = new People();{0}People p2 = new People();{1}", System.Environment.NewLine, System.Environment.NewLine); Console.WriteLine("p1.IDCode={0}", p1.IDCode); Console.WriteLine("p2.IDCode={0}", p2.IDCode); Console.WriteLine(); Console.WriteLine("p1==p2:{0}【保留引用地址的對比】", p1 == p2); Console.WriteLine("p1.Equals(p2):{0}【過載比較IDCode,值型別比較效果】{1}", p1.Equals(p2), System.Environment.NewLine); Console.WriteLine("----------------------------------"); p1 = p2; p1.IDCode = "No2"; Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("p1.IDCode={0}", p1.IDCode); Console.WriteLine("p2.IDCode={0}", p2.IDCode); Console.WriteLine(); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("p1=p2;{0}", System.Environment.NewLine); Console.WriteLine("p1==p2:{0}", p1 == p2); Console.WriteLine("p1.Equals(p2):{0}", p1.Equals(p2)); Console.ReadKey();
執行:
還有,Object.ReferenceEquals方法比較例項是否相同。驗證引用的相等性。
三、建議12:重寫Equals時也要重寫GetHashCode
當我們重寫Equals時,編譯器會提示一條警告:
為什麼會有這樣的提示?
因為在 System.Collections.Hashtable型別和System.Collections .Generic.Dictionary型別以及一些其他的集合類,要求兩個物件相等,必須具有相同的雜湊碼。
所以在重寫Equals時,也應該重寫GetashCode,確保相等性的演算法和物件雜湊碼演算法一致。
新增:新增一個新的鍵值對時,首先會獲取物件的雜湊碼,這個雜湊碼指出鍵值對應該存在哪一個雜湊桶中。
查詢:查詢集合的一個鍵時,也獲取指定鍵物件的雜湊碼,這個雜湊碼指定了我們查詢鍵值對所存在哪一個雜湊桶中。所以我們就去雜湊桶中搜尋與當前指定的鍵物件的雜湊碼。
看一下下面這個例項:
class Program { static Dictionary<People, string> ppdic = new Dictionary<People, string>(); static void AddPP() { People p = new People(){Name="Sun"}; ppdic.Add(p, "Sun");
//Console.WriteLine(p.GetHashCode());
Console.WriteLine(ppdic.ContainsKey(p)); } static void Main(string[] args) { AddPP(); People pp = new People() { Name = "Sun" };
//Console.WriteLine(pp.GetHashCode()); Console.WriteLine(ppdic.ContainsKey(pp)); Console.ReadKey(); } }
class People
{
public String Name { get; set; }
public string IDCode { get; set; }
public override bool Equals(object obj)
{
People p = obj as People;
return p.IDCode == IDCode;
}
}
這裡,我們重寫了People類的Equals方法,客戶端中,首先呼叫了AddPP()方法,新增一個Name="Sun"的People物件.
緊跟著,我們也定義了同樣一個Name="Sun"的People物件。因該說兩次People物件一樣,所以兩次輸出都應該為True.
(這裡我們不重寫Equals效果也是一樣的,但這裡的重點是說明:GetHashCode)
為什麼相同的物件返回的結果不一樣?其實上面說過了,CLR會為每個物件建立唯一的雜湊碼(在生存週期內),因為當前類沒有重寫GetHashCode方法,所以會呼叫Object的GetHashCode。
解開上面的兩句註釋,執行:我們看到兩個例項物件(p、pp)的雜湊碼是不一樣的。
如果我們定義的自定義型別會被用作字典等型別的Key值,那我們可能需要重寫Equals的同時還要重寫GetHashCode。以來正確地實現我們的需求。
class People { public String Name { get; set; } public string IDCode { get; set; } public override bool Equals(object obj) { People p = obj as People; return p.IDCode == IDCode; } public override int GetHashCode() { if (IDCode != null) return this.IDCode.GetHashCode(); return base.GetHashCode(); } }
為了產生更好的雜湊值的隨機分佈:
public override int GetHashCode() { if (IDCode != null) return (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "$" + this.IDCode).GetHashCode(); return base.GetHashCode(); }
當然最後我們也最好實現繼承型別安全介面:IEquatable<People>
我們的身份證IDCode設計為只讀屬性,例項化時跟隨一個身份證。
最終版本:
class People:IEquatable<People> { public People(string idCode) { this._idCode = idCode; } public String Name { get; set; } private string _idCode; public string IDCode { get; private set; } public override bool Equals(object obj) { People p = obj as People; return p.IDCode == IDCode; } public override int GetHashCode() { if (IDCode != null) return (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "$" + this.IDCode).GetHashCode(); return base.GetHashCode(); } public bool Equals(People other) { return IDCode == other.IDCode; } }