讀改善c#程式碼157個建議:建議10~12

K戰神發表於2015-07-17

目錄:

  • 建議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;
        }
    }

 

相關文章