前言

  本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要学习记录以下内容:

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

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

  建议12、重写Equals时也要重写GetHashCode

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

  有对象的地方就会存在比较,就像小时候每次拿着考卷回家,妈妈都会问你隔壁的那谁谁谁考了多少分呀。下面我们也来举个简单的例子,就是有几个人的Salary列表。我们根据基本工资来进行罗列:

    class Program
{
static void Main(string[] args)
{
ArrayList array = new ArrayList();
array.Add();
array.Add();
array.Add();
array.Sort();
foreach (var obj in array)
{
Console.WriteLine(obj.ToString());
}
Console.ReadLine();
}
}

 可以发现通过ArrayList.Sort()方法即可完成排序的任务。不过ArrayList这里只能是一个字段的。假如有姓名、工资两个字段,然后根据工资进行排序那么按照现在的情况来看,ArrayList是无法实现的。所以接口IComparable现在可以派上用场了。现在先定义一个实体,并且实现接口IComparable。

public class Salary:IComparable
{
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; } /// <summary>
/// 基本工资
/// </summary>
public int BaseSalary { get; set; } /// <summary>
/// 实现IComparable接口
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public int CompareTo(object obj)
{
Salary staff = obj as Salary;
if (BaseSalary > staff.BaseSalary)
{
return ;
}
else if (BaseSalary == staff.BaseSalary)
{
return ;
}
else
{
return -;
}
}
}

进行排序

                ArrayList array = new ArrayList();
array.Add(new Salary() { Name = "aehyok", BaseSalary = });
array.Add(new Salary() { Name = "Kris", BaseSalary = });
array.Add(new Salary() { Name = "Leo", BaseSalary = });
array.Add(new Salary() { Name = "Niki", BaseSalary = });
array.Sort();
foreach (Salary obj in array)
{
Console.WriteLine(string.Format("{0} BaseSalary:{1}", obj.Name, obj.BaseSalary));
}
Console.ReadLine();

如果未继承Icomparable接口。那么会出现如下错误。

正确的进行排序,结果如下所示

假如现在在Salary类中添加了一个奖金的字段如下

public class Salary:IComparable
{
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; } /// <summary>
/// 基本工资
/// </summary>
public int BaseSalary { get; set; } /// <summary>
/// 奖金
/// </summary>
public int Bouns { get; set; } /// <summary>
/// 实现IComparable接口
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public int CompareTo(object obj)
{
Salary staff = obj as Salary;
if (BaseSalary > staff.BaseSalary)
{
return ;
}
else if (BaseSalary == staff.BaseSalary)
{
return ;
}
else
{
return -;
}
}
}

再继续假如,现在又要以Bouns奖金字段进行排序,那应该怎么处理呢?当然修改Salary实体类中继承的接口方法进行处理肯定是没问题了,但是比较麻烦。我们可以采用自定义比较接口IComparer来实现。

    public class BounsComparer:IComparer
{
public int Compare(object x, object y)
{
Salary s1 = x as Salary;
Salary s2 = y as Salary;
return s1.Bouns.CompareTo(s2.Bouns);
}
}

然后重新进行排序

                ArrayList array = new ArrayList();
array.Add(new Salary() { Name = "aehyok", BaseSalary = ,Bouns= });
array.Add(new Salary() { Name = "Kris", BaseSalary = ,Bouns= });
array.Add(new Salary() { Name = "Leo", BaseSalary = ,Bouns= });
array.Add(new Salary() { Name = "Niki", BaseSalary = ,Bouns= });
array.Sort(new BounsComparer());
foreach (Salary obj in array)
{
Console.WriteLine(string.Format("{0} \tBaseSalary:{1}\tBouns{2}", obj.Name, obj.BaseSalary,obj.Bouns));
}
Console.ReadLine();

结果如下所示

注意,刚才实现接口名字叫IComparable,而自定义的比较器接口是IComparer

如果我们稍有经验,会发现如下函数中的问题

        public int Compare(object x, object y)
{
Salary s1 = x as Salary;
Salary s2 = y as Salary;
return s1.Bouns.CompareTo(s2.Bouns);
}

这个函数中进行了转型处理,这是会影响性能的。如果集合中有成千上万个复杂的实体对象,那么进行排序时耗费的时间是巨大的。所以泛型登场,很好的解决了这个问题。

因此以上代码中的ArrayList,可以替换为List<T>,对应的我们就应该实现IComparable<T>和IComparer<T>。

实现的代码如下:

1、实体类实现接口IComparable<T>  2、自定义比较器实现接口IComparer<T> 3、进行排序的调用

    public class Salary:IComparable<Salary>
{
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; } /// <summary>
/// 基本工资
/// </summary>
public int BaseSalary { get; set; } /// <summary>
/// 奖金
/// </summary>
public int Bouns { get; set; } /// <summary>
/// 实现IComparable接口
/// </summary>
/// <param name="obj"></param>
/// <returns></returns> public int CompareTo(Salary other)
{
return BaseSalary.CompareTo(other.BaseSalary);
}
}
    public class BounsComparer : IComparer<Salary>
{
public int Compare(Salary x, Salary y)
{
return x.Bouns.CompareTo(y.Bouns);
}
}
                List<Salary> array =new List<Salary>();
array.Add(new Salary() { Name = "aehyok", BaseSalary = ,Bouns= });
array.Add(new Salary() { Name = "Kris", BaseSalary = ,Bouns= });
array.Add(new Salary() { Name = "Leo", BaseSalary = ,Bouns= });
array.Add(new Salary() { Name = "Niki", BaseSalary = ,Bouns= });
array.Sort(new BounsComparer());
foreach (Salary obj in array)
{
Console.WriteLine(string.Format("{0} \tBaseSalary:{1}\tBouns{2}", obj.Name, obj.BaseSalary,obj.Bouns));
}
Console.ReadLine();

最终结果

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

这里我之前有一篇博文针对==和Equals有过专门的介绍,在此就不再进行过多的阐述了http://www.cnblogs.com/aehyok/p/3505000.html

建议12、重写Equals时也要重写GetHashCode

下面先来看一个简单的小例子,定义如下实体类:

    public class Person
{
public string IDCode { get;private set; } public Person(string idCode)
{
this.IDCode = idCode;
} public override bool Equals(object obj)
{
return IDCode == (obj as Person).IDCode;
}
}

针对上面实体类进行编译

这里会有一个提示暂时先不管

    public class PersonMoreInfo
{
public string SomeThing { get; set; }
}

通过这两个实体类,我们来使用以下Dictionary类型,代码如下:

    class Program
{
static Dictionary<Person, PersonMoreInfo> PersonValues = new Dictionary<Person, PersonMoreInfo>();
static void Main(string[] args)
{
AddAPerson();
Person mike = new Person("aehyok");
Console.WriteLine(PersonValues.ContainsKey(mike));
//Console.WriteLine(mike.GetHashCode());
Console.ReadLine();
}
static void AddAPerson()
{
Person mike = new Person("aehyok");
PersonMoreInfo mikeValue = new PersonMoreInfo() { SomeThing="aehyok's Info"};
PersonValues.Add(mike, mikeValue);
//Console.WriteLine(mike.GetHashCode());
Console.WriteLine(PersonValues.ContainsKey(mike));
}
}

结果为true,false。

理论上来说,我们重写了Person类中的Equals方法,也就是说在AddAPerson方法中的mike和在Main函数中的mike属于”值相等“。从上面的结果可以发现,针对同一个实例,这种结论是正确的,针对不同的实例,这种结果就是有问题的。

基于键值的集合(如上面的Dictionary)会根据Key值来查找Value值。CLR内部会优化这种查找,实际上,最终是根据Key值的HasCode来查找Value值。代码运行的时候,CRL首先会调用Person类型的GetHashCode,由于发现Person没有实现GetHashCode,所以CLR最终会调用Object的 GetHashCode方法。将上面代码中的两行注释代码去掉,运行程序得到输出

可以发现,AddAPerson方法和Main方法中的两个mike的HashCode是不同的。这是因为:Object为所有的CLR类型都提供了GetHashCode的默认实现。每new一个对象,CLR都会为该对象生成一个固定的整形值,该整形值在对象的生存周期内不会改变,而该对象默认的GetHashCode实现就是对该整型值求HashCode。所以,在上面的代码中,两个mike兑现虽然属性值都一致,但是它们默认实现的HashCode不一致,这就导致Dictionary中出现异常的行为。

想要修正该问题,就必须重写GetHashCode方法。Person类的一个简单的重写可以是如下的代码:

        public override int GetHashCode()
{
return this.IDCode.GetHashCode();
}

此时再运行上面的代码,会发现

两者的HashCode是一致的,而dictionary也会找到相应的键值。

GetHasCode方法存在另外一个问题,就是它永远只返回一个整型,而整型类型的容量显然无法满足字符串的容量

            string str1 = "NB0903100006";
string str2 = "NB0904140001";
Console.WriteLine(str1.GetHashCode());
Console.WriteLine(str2.GetHashCode());

这两个字符串产生的HasCode是一样的。为了减少这种情况,我们稍作修改:

        public override int GetHashCode()
{
return (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName+"#"+this.IDCode).GetHashCode();
}

重写Equals方法的同时,也应该实现一个类型安全的接口IEquatable<T>,所以Person类型的最终代码如下:

    public class Person:IEquatable<Person>
{
public string IDCode { get;private set; } public Person(string idCode)
{
this.IDCode = idCode;
} public override bool Equals(object obj)
{
return IDCode == (obj as Person).IDCode;
} public override int GetHashCode()
{
return (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName+"#"+this.IDCode).GetHashCode();
} public bool Equals(Person other)
{
return IDCode == other.IDCode;
}
}

对于IEquatable接口暂时没接触过。第一次使用。

编写高质量代码改善C#程序的157个建议[10-12]的更多相关文章

  1. 编写高质量代码改善C#程序的157个建议[1-3]

    原文:编写高质量代码改善C#程序的157个建议[1-3] 前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理 ...

  2. 读书--编写高质量代码 改善C#程序的157个建议

    最近读了陆敏技写的一本书<<编写高质量代码  改善C#程序的157个建议>>书写的很好.我还看了他的博客http://www.cnblogs.com/luminji . 前面部 ...

  3. 编写高质量代码改善C#程序的157个建议——建议157:从写第一个界面开始,就进行自动化测试

    建议157:从写第一个界面开始,就进行自动化测试 如果说单元测试是白盒测试,那么自动化测试就是黑盒测试.黑盒测试要求捕捉界面上的控件句柄,并对其进行编码,以达到模拟人工操作的目的.具体的自动化测试请学 ...

  4. 编写高质量代码改善C#程序的157个建议——建议156:利用特性为应用程序提供多个版本

    建议156:利用特性为应用程序提供多个版本 基于如下理由,需要为应用程序提供多个版本: 应用程序有体验版和完整功能版. 应用程序在迭代过程中需要屏蔽一些不成熟的功能. 假设我们的应用程序共有两类功能: ...

  5. 编写高质量代码改善C#程序的157个建议——建议155:随生产代码一起提交单元测试代码

    建议155:随生产代码一起提交单元测试代码 首先提出一个问题:我们害怕修改代码吗?是否曾经无数次面对乱糟糟的代码,下决心进行重构,然后在一个月后的某个周一,却收到来自测试版的报告:新的版本,没有之前的 ...

  6. 编写高质量代码改善C#程序的157个建议——建议154:不要过度设计,在敏捷中体会重构的乐趣

    建议154:不要过度设计,在敏捷中体会重构的乐趣 有时候,我们不得不随时更改软件的设计: 如果项目是针对某个大型机构的,不同级别的软件使用者,会提出不同的需求,或者随着关键岗位人员的更替,需求也会随个 ...

  7. 编写高质量代码改善C#程序的157个建议——建议153:若抛出异常,则必须要注释

    建议153:若抛出异常,则必须要注释 有一种必须加注释的场景,即使异常.如果API抛出异常,则必须给出注释.调用者必须通过注释才能知道如何处理那些专有的异常.通常,即便良好的命名也不可能告诉我们方法会 ...

  8. 编写高质量代码改善C#程序的157个建议——建议152:最少,甚至是不要注释

    建议152:最少,甚至是不要注释 以往,我们在代码中不写上几行注释,就会被认为是钟不负责任的态度.现在,这种观点正在改变.试想,如果我们所有的命名全部采用有意义的单词或词组,注释还有多少存在的价值. ...

  9. 编写高质量代码改善C#程序的157个建议——建议151:使用事件访问器替换公开的事件成员变量

    建议151:使用事件访问器替换公开的事件成员变量 事件访问器包含两部分内容:添加访问器和删除访问器.如果涉及公开的事件字段,应该始终使用事件访问器.代码如下所示: class SampleClass ...

  10. 编写高质量代码改善C#程序的157个建议——建议150:使用匿名方法、Lambda表达式代替方法

    建议150:使用匿名方法.Lambda表达式代替方法 方法体如果过小(如小于3行),专门为此定义一个方法就会显得过于繁琐.比如: static void SampeMethod() { List< ...

随机推荐

  1. python strip() lstrip() rstrip() 使用方法

    Python中的strip用于去除字符串的首尾字符串,同理,lstrip用于去除最左边的字符,rstrip用于去除最右边的字符. 这三个函数都可传入一个参数,指定要去除的首尾字符. 需要注意的是,传入 ...

  2. libc.so.6被删后导致系统无法使用的原因及解决方法

    记一次升级glibc库发生的错误 今天给glibc库升级,发生了一件让我吓出一声汗的事情,我把动态库中的libc.so.6给删了,瞬间所有的非系统命令都无法使用,使用就报错 当时就吓尿了,生产环境被我 ...

  3. Load xlsx in a folder to RDBMS table with Talend

    Step 1 . Use tFileList component to get the file list. And set proper property. Step 2. Use tFileInp ...

  4. 如何利用ZBrush中的DynaMesh创建身体(一)

    之前的ZBrush教程中我们用Extract抽出功能演示了头发的立体雕刻方法,本讲将对已完成的头部模型添加躯干,使用DynaMesh创建身体的方法,以及人体比例和结构的介绍. 查看详细的视频教程可直接 ...

  5. 推荐——Monkey《大话 app 测试——Android、iOS 应用测试指南》

    <大话移动——Android与iOS应用测试指南> 京东可以预购啦!http://item.jd.com/11495028.html 当当网:http://product.dangdang ...

  6. leetcode-Single Number III 找独数

    Single Number III Given an array of numbers nums, in which exactly two elements appear only once and ...

  7. AC日记——机器翻译 洛谷 P1540

    题目背景 小晨的电脑上安装了一个机器翻译软件,他经常用这个软件来翻译英语文章. 题目描述 这个翻译软件的原理很简单,它只是从头到尾,依次将每个英文单词用对应的中文含义来替换.对于每个英文单词,软件会先 ...

  8. finder怎么才能找到library

    右键Finder——前往目录 输入~/Library

  9. C++容器的复制

    C++容器的复制不同于Java Java是引用复制,复制的仅仅是对象的引用, 在需要复制容器内对象的副本集合的情况,需要使用Clone方法,而且要注意clone方法的浅拷贝 深拷贝 C++的容器复制 ...

  10. 实例化Model的三种方式