编写高质量C#代码学习笔记(3)

建议10: 创建对象时需要考虑是否实现比较器

有对象的地方就会存在比较,在.NET的世界中也一样。举个最简单的例子,在UI中,有一个10个人的Salary列表。根据排序的需要,列表要支持针对基本工资来罗列Salary。这个时候,接口IComparable就会起作用,代码如下所示:

  
  
  1. class Salary : IComparable  
  2. {  
  3.     public string Name { get; set; }  
  4.     public int BaseSalary { get; set; }  
  5.     public int Bonus { get; set; }  
  6.  
  7.     #region IComparable 成员  
  8.  
  9.     public int CompareTo(object obj)  
  10.     {  
  11.         Salary staff = obj as Salary;  
  12.         if (BaseSalary > staff.BaseSalary)  
  13.         {  
  14.             return 1;  
  15.         }  
  16.         else if (BaseSalary == staff.BaseSalary)  
  17.         {  
  18.             return 0;  
  19.         }  
  20.         else  
  21.         {  
  22.             return -1;  
  23.         }  
  24.         //return BaseSalary.CompareTo(staff.BaseSalary);  
  25.     }  
  26.  
  27.     #endregion  

注意 上面代码中CompareTo方法有一条注释的代码,其实本方法完全可以使用该注释代码代替,因为利用了整型的默认比较方法。此处未使用本注释代码,是为了更好地说明比较器的工作原理。

实现了接口IComparable后,我们就可以根据BaseSalary对Salary进行排序了,代码如下所示:

  
  
  1. ArrayList companySalary = new ArrayList();  
  2. companySalary.Add(new Salary() { Name = "Mike"BaseSalary = 3000 });  
  3. companySalary.Add(new Salary() { Name = "Rose"BaseSalary = 2000 });  
  4. companySalary.Add(new Salary() { Name = "Jeffry"BaseSalary = 1000 });  
  5. companySalary.Add(new Salary() { Name = "Steve"BaseSalary = 4000 });  
  6. companySalary.Sort();  
  7. foreach (Salary item in companySalary)  
  8. {  
  9.     Console.WriteLine(item.Name + "\t BaseSalary: " + item.BaseSalary.ToString());  

上面代码的输出如下:

  
  
  1. Jeffry   BaseSalary: 1000  
  2. Rose     BaseSalary: 2000  
  3. Mike     BaseSalary: 3000  
  4. Steve    BaseSalary: 4000 

现在,问题来了:如果不想以基本工资BaseSalary进行排序,而是以奖金Bonus进行排序,该如何处理呢?这个时候,接口IComparer的作用就体现出来了,可以使用IComparer来实现一个自定义的比较器。如下所示:
  
  
  1. class BonusComparer : IComparer  
  2. {  
  3.     #region IComparer 成员  
  4.  
  5.     public int Compare(object x, object y)  
  6.     {  
  7.         Salary s1 = x as Salary;  
  8.         Salary s2 = y as Salary;  
  9.         return s1.Bonus.CompareTo(s2.Bonus);  
  10.     }  
  11.  
  12.     #endregion  

我们在排序的时候为Sort方法提供此比较器,代码如下所示:
  
  
  1. ArrayList companySalary = new ArrayList();  
  2. companySalary.Add(new Salary() { Name = "Mike"BaseSalary = 3000,  
  3.     Bonus = 1000 });  
  4. companySalary.Add(new Salary() { Name = "Rose"BaseSalary = 2000,  
  5.     Bonus = 4000 });  
  6. companySalary.Add(new Salary() { Name = "Jeffry"BaseSalary = 1000,  
  7.     Bonus = 6000 });  
  8. companySalary.Add(new Salary() { Name = "Steve"BaseSalary = 4000,  
  9.     Bonus = 3000 });  
  10. companySalary.Sort(new BonusComparer());    //提供  
  11.                                 //一个非默认的比较器  
  12. foreach (Salary item in companySalary)  
  13. {  
  14.     Console.WriteLine(string.Format("Name:{0} \tBaseSalary:{1} \tBonus:{2}",  
  15.         item.Name, item.BaseSalary, item.Bonus));  

输出结果如下:
  
  
  1. Name:Mike       BaseSalary:3000         Bonus:1000  
  2. Name:Steve      BaseSalary:4000         Bonus:3000  
  3. Name:Rose       BaseSalary:2000         Bonus:4000  
  4. Name:Jeffry     BaseSalary:1000         Bonus:6000 

如果我们稍有经验,就会发现上面的代码使用了一个已经不建议使用的集合类ArrayList(当泛型出来后,就建议尽量不使用所有非泛型集合类)。至于原因,从上面的代码中我们也可以看出端倪。 注意查看代码中的Compare函数,如:
  
  
  1. public int Compare(object x, object y)  
  2. {  
  3.     Salary s1 = x as Salary;  
  4.     Salary s2 = y as Salary;  
  5.     return s1.Bonus.CompareTo(s2.Bonus);  

我们发现这个函数进行了转型,这是会影响性能的。如果集合中有成千上万个复杂的实体对象,在排序的时候所耗费掉的性能就是可观的;而泛型的出现,可以避免运行时转型。 因此,以上代码中的ArrayList,应该换成List,对应地,我们就该实现IComparable和IComparer。最终的代码应该像下面这样:
  
  
  1. static void Main(string[] args)  
  2. {  
  3.     List<Salary> companySalary = new List<Salary>()  
  4.         {  
  5.             new Salary() { Name = "Mike"BaseSalary = 3000Bonus = 1000 },  
  6.             new Salary() { Name = "Rose"BaseSalary = 2000Bonus = 4000 },  
  7.             new Salary() { Name = "Jeffry"BaseSalary = 1000Bonus = 6000 },  
  8.             new Salary() { Name = "Steve"BaseSalary = 4000Bonus = 3000 }  
  9.         };  
  10.     companySalary.Sort(new BonusComparer());    //提供一个非默认的比较器  
  11.     foreach (Salary item in companySalary)  
  12.     {  
  13.         Console.WriteLine(string.Format("Name:{0} \tBaseSalary:{1} \tBonus:{2}",  
  14.             item.Name, item.BaseSalary, item.Bonus));  
  15.     }  
  16. }  
  17.  
  18. class Salary : IComparable<Salary> 
  19. {  
  20.     public string Name { get; set; }  
  21.     public int BaseSalary { get; set; }  
  22.     public int Bonus { get; set; }  
  23.  
  24.     #region IComparable<Salary> 成员  
  25.  
  26.     public int CompareTo(Salary other)  
  27.     {  
  28.         return BaseSalary.CompareTo(other.BaseSalary);  
  29.     }  
  30.  
  31.     #endregion  
  32. }  
  33.  
  34. class BonusComparer : IComparer<Salary> 
  35. {  
  36.     #region IComparer<Salary> 成员  
  37.  
  38.     public int Compare(Salary x, Salary y)  
  39.     {  
  40.         return x.Bonus.CompareTo(y.Bonus);  
  41.     }  
  42.  
  43.      #endregion  

建议11: 区别对待==和Equals

在开始本建议之前,首先要明确概念“相等性”。CLR中将“相等性”分为两类:“值相等性”和“引用相等性”。如果用来比较的两个变量所包含的数值相等,那么将其定义为“值相等性”;如果比较的两个变量引用的是内存中的同一个对象,那么将其定义为“引用相等性”。

无论是操作符“==”还是方法“Equals”,都倾向于表达这样一个原则:

对于值类型,如果类型的值相等,就应该返回True。

对于引用类型,如果类型指向同一个对象,则返回True。

下面的代码输出所遵循的就是以上原则:

   
   
  1. static void ValueTypeOPEquals()  
  2. {  
  3.     int i = 1;  
  4.     int j = 1;  
  5.     //True  
  6.     Console.WriteLine(i == j);  
  7.     j = i;  
  8.     //True  
  9.     Console.WriteLine(i == j);  
  10. }  
  11.  
  12. static void ReferenceTypeOPEquals()  
  13. {  
  14.     object a = 1;  
  15.     object b = 1;  
  16.     //False  
  17.     Console.WriteLine(a == b);  
  18.     b = a;  
  19.     //True  
  20.     Console.WriteLine(a == b);  
  21. }  
  22.  
  23. static void ValueTypeEquals()  
  24. {  
  25.     int i = 1;  
  26.     int j = 1;  
  27.     //True  
  28.     Console.WriteLine(i.Equals(j));  
  29.     j = i;  
  30.     //True  
  31.     Console.WriteLine(i.Equals(j));  
  32. }  
  33.  
  34.  static void ReferenceTypeEquals()  
  35. {  
  36.     object a = new Person("NB123");  
  37.     object b = new Person("NB123");  
  38.     //False  
  39.     Console.WriteLine(a.Equals(b));  
  40.     b = a;  
  41.     //True  
  42.     Console.WriteLine(a.Equals(b));  

但是,我们同时也要了解,无论是操作符“==”还是“Equals”方法都是可以被重载的。比如,对于string这样一个特殊的引用类型,微软觉得它的现实意义更接近于值类型,所以,在FCL中,string的比较被重载为针对“类型的值”的比较,而不是针对“引用本身”的比较。

从设计上来说,很多自定义的类型(尤其是自定义的引用类型)会存在和string类型比较接近的情况。如例子中所举的类型Person,在现实生活中,如果两者的IDCode是相等的,我们就认为两者是同一个人,这个时候,就要重载Equals这个方法,代码如下所示:

   
   
  1. class Person  
  2. {  
  3.     public string IDCode { get; private set; }  
  4.  
  5.     public Person(string idCode)  
  6.     {  
  7.         this.IDCode = idCode;  
  8.     }  
  9.  
  10.     public override bool Equals(object obj)  
  11.     {  
  12.         return IDCode == (obj as Person).IDCode;  
  13.     }  

这时,再通过Equals去比较两个具有相同IDCode的Person对象的值,返回的就会是true,代码如下所示:

   
   
  1. object a = new Person("NB123");  
  2. object b = new Person("NB123");  
  3. //False  
  4. Console.WriteLine(a == b);  
  5. // True  
  6. Console.WriteLine(a.Equals(b)); 

这里,再引出操作符“==”和“Equals”方法之间的一点区别。一般来说,对于引用类型,我们要定义“值相等性”,应该仅仅去重载Equals方法,同时让“==”表示“引用相等性”。

注意 由于操作符“==”和“Equals”方法从语法实现上来说,都可以被重载为表示“值相等性”和“引用相等性”。所以,为了明确有一种方法肯定比较的是“引用相等性”,FCL中提供了Object. ReferenceEquals方法。该方法比较的是:两个示例是否是同一个示例。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值