一:对象相等性和同一性

System.Object提供了名为Equals的虚方法,作用是在两个对象包含相同值的前提下返回true,内部实现

public class Object
{
public virtual Boolean Equals(Object obj)
{
//比较两个引用指向同一个对象,他们肯定包含相同的值
if (this == obj) return true; //假定对象不包含相同的值
return false;
}
}

乍一看,这似乎就是Euqals的合理实现,假如this和obj实参引用同一个对象,就返回true,似乎合理是因为Equals知道对象

肯定包含和它一样的值,但假如实参引用不同对象,Equals就不肯定对象是否包含相同的值,所以返回false,换言之,

对于Object的Equals方法的默认实现:它实现的实际是同一性,而非相等性。

所以理想的实现应该是下面这样:

public class Object
{
public virtual Boolean Equals(object obj)
{
//要比较的对象不能为空
if (obj == null) return false; //如果对象属于不同类型,则肯定不相等
if (this.GetType() != obj.GetType()) return false; //如果对象属于相同的类型,那么在它们所有字段都匹配的前提下返回true
//由于System.Object没有定义任何字段,所有字段是匹配的
return true;
}
}

类型重写Equals方法时应调用其基类的Equals实现(除非基类就是Object),另外,由于类型能够重写Object的Euqals方法,

所以不能再用它来测试同一性。

为了解决这个问题,Object 提供了静态方法ReferenceEquals,其原型如下:

public class Object
{
pubic static Boolean ReferenceEquals(Object objA, Object objB)
{
return (objA == objB)
}
}

检查同一性(看两个引用是否指向同一个对象)务必调用ReferenceEquals

不应该使用C#的==操作符(除非先把这两个操作数都转型为Object),因为某个操作数的类型可能重载了==操作符,

为其赋予不同于“同一性”的语义。

可以看到,在涉及对象相等性和同一性的时候,.NET Framework 的设计很容易使人混淆。

System.ValueType(所有值类型的基类)就重写了Object的Equals方法,并进行了正确的实现来执行值的相等性检查(而不是同一性检查)。

内部实现:

[SecuritySafeCritical]
[__DynamicallyInvokable]
public override bool Equals(object obj)
{
if (obj == null)
return false;
RuntimeType type = (RuntimeType) this.GetType();
if ((RuntimeType) obj.GetType() != type)
return false;
object a = (object) this;
if (ValueType.CanCompareBits((object) this))
return ValueType.FastEqualsCheck(a, obj);
FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
//比较类型的每个实例字段,任何字段不相等,都返回false
for (int index = ; index < fields.Length; ++index)
{
object obj1 = ((RtFieldInfo) fields[index]).UnsafeGetValue(a);
object obj2 = ((RtFieldInfo) fields[index]).UnsafeGetValue(obj);
if (obj1 == null)
{
if (obj2 != null)
return false;
}
else if (!obj1.Equals(obj2))
return false;
}
return true;
}

可以看到,ValueType的Equals方法利用反射,由于反射机制慢的原因,应该定义自己的值类型时重写Equals方法来提供

自己的实现,从而提高值类型相等性比较的性能。重写Equals时,可能还需要实现一下方法:

让类型实现System.IEquatable<T>接口的Equals方法

这个泛型接口允许定义类型安全的Equals方法

重载==和!=操作符方法

通常应实现这些操作符方法,在内部调用类型安全的Equals

如果出于排序的目的而比较类型的实例,类型还应实现System.IComparable的CompareTo方法和System.IComparable<T>类型

安全的CompareTo,如果实现了这些方法,还可考虑重载各种比较操作符方法(<,<=,>,>=),这些方法内部调用类型安全的CompareTo。

对于值类型来说,经过上面讨论,总结以下的一些规范:

1:要为值类型是实现Equatable<T>

2:要在实现IEquatable<T>的同时覆盖Object.Equals

//Equals的这两个重载方法因该具有完全相同的语义
public struct PositiveInt32 : IEquatable<PositiveInt32>
{
public bool Equals(PositiveInt32 other)
{ }
public override bool Equals(object obj)
{
if (!(obj is PositiveInt32)) return false;
return Equals((PositiveInt32)obj);
}
}

3:考虑在实现 IEquatable<T>的同时重载 operator==和 operator!=

 public struct Decimal : IEquatable<Decimal>
{
public bool Equals(Decimal other)
{ } public static bool operator ==(Decimal x, Decimal y)
{
return x.Equals(y);
} public static bool operator !=(Decimal x, Decimal y)
{
return !x.Equals(y);
}
}

4:要在实现 IComparable<T>的同时实现IEquatable<T>
注意,由于并非所有的类型都支持排序,因此本条规范反过来并不成立。

public struct Decimal : IComparable<Decimal>, IEquatable<Decimal> { ...}

5:考虑在实现 IComparable<T>的同时重载比较操作符(<、>、<=、>=)

 public struct Decimal : IComparable<Decimal>
{
public int CompareTo(Decimal other) { ...} public static bool operator < (Decimal x, Decimal y)
{
return x.CompareTo(y) < ;
} public static bool operator >(Decimal x, Decimal y)
{
return x.CompareTo(y) > ;
}
}

对于引用类型来说,总结以下的一些规范:

1:考虑覆盖Equals以提供值相等语义——如果引用类型表示的是一个值。例如,对那些表示数值或其他数学实体的引用类型来说,

      可以考虑覆盖Equa1s方法。

2:不要为可变的引用类型实现值相等语义。实现值相等语义的引用类型应该是不可变的(比如 System. String)。

      例如,对具备值相等语义的可变引用类型来说,当它们的值改变时(因此散列码也发生改变),它们可能会在散列表中“丢失”。

二:区别对待==和Equals

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

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

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

操作符“==”和“ Equals”方法都是可以被重载的

有些引用类型对默认实现进行了覆盖,以提供值相等的语义,例如,由于字符串的值是由字符串中的字符决定的,

因此任何两个字符串实例只要包含完全相同的字符排列,String类的Equals方法就会返回true。

在现实生活中,如果两者的 IDCode是相等的,我们就认为两者是同一个人,这个时候,就要重载 Equals这个方法,代码如下所示:

class Person
{
public string IDCode { get; private set; } public Person(string idCode)
{
this.IDCode = idCode;
} public override bool Equals(object obj)
{
Person p = obj as Person;
return this.IDCode == p.IDCode;
} /*这里,不要像下面这样再定义操作符“==”和“!=”的重载。
* 一般来说,对于引用类型,我们要定义“值相等性”,应该仅仅去重载 Equals方法,同时让“=”表示“引用相等性”。*/
public static bool operator ==(Person p1, Person p2)
{
return p1.IDCode == p2.IDCode;
} public static bool operator !=(Person p1, Person p2)
{
return p1.IDCode != p2.IDCode;
}
}

 三:实现对象比较器

上面提到System.IComparable的CompareTo方法实现比较,下面看一下如何实现,

class Salary : IComparable<Salary>
{
public string Name { get; set; } public int BaseSalary { get; set; } public int Bonus { get; set; } public int CompareTo(Salary other)
{
return this.BaseSalary.CompareTo(other.BaseSalary);
}
}

实现了IComparable后,我们就可以根据BaseSalary对Salary进行排序了,

class Program
{
static void Main(string[] args)
{
List<Salary> lists = new List<Salary>()
{
new Salary(){ Name = "Mike", BaseSalary = , Bonus = },
new Salary(){ Name = "Rose", BaseSalary = , Bonus = },
new Salary(){ Name = "Jeffry", BaseSalary = , Bonus = }
}; lists.Sort();
}
}

如果想以Bonus进行排序,除了修改上面的程序,还可以使用IComparer实现自定义比较器,

class Salary : IComparer<Salary>
{
public string Name { get; set; } public int BaseSalary { get; set; } public int Bonus { get; set; } public int Compare(Salary x, Salary y)
{
return x.Bonus.CompareTo(y.Bonus);
}
}

实现了IComparer后,我们就可以根据Bonus对Salary进行排序了,

class Program
{
static void Main(string[] args)
{
List<Salary> lists = new List<Salary>()
{
new Salary(){ Name = "Mike", BaseSalary = , Bonus = },
new Salary(){ Name = "Rose", BaseSalary = , Bonus = },
new Salary(){ Name = "Jeffry", BaseSalary = , Bonus = }
}; lists.Sort(new BonusComparer());
}
}

Sort默认是正序排序,如果想倒叙排序,只需在返回前加负号即可,因为CompareTo方法返回的是-1,0,1。

return -x.Bonus.CompareTo(y.Bonus);

如果是类似List<string>或者List<int>进行排序:

正序:list.Sort();

倒叙:list.Sort((x,y) => -x.CompareTo(y));

另外,对集合的排序还可以用集合的扩展方法OrderBy或OrderByDescending实现

c# 对象相等性和同一性的更多相关文章

  1. .NET C#基础(1):相等性与同一性判定 - 似乎有点小缺陷的设计

    0. 文章目的   本文面向有一定.NET C#基础知识的学习者,介绍在C#中的常用的对象比较手段,并提供一些编码上的建议. 1. 阅读基础 1:理解C#基本语法与基本概念(如类.方法.字段与变量声明 ...

  2. 快学Scala 第十三课 (类型层级,对象相等性)

    Scala 类型层级: 对象相等性: 和Java一样要重写equals方法和hashcode方法 class Student(val id: Int, val name: String) { over ...

  3. 对象比较中 "相等性"和"同一性" 生动地解释

    对象们都住在不同的房间里,每个房间只能住一个对象.对象们都被锁在房间里,永远没有办法搬家(至少从我们讨论的角度来说,这个说法是正确的).所以如果你知道了一个对象的房间号,就能找到对应的对象. 现在假如 ...

  4. C# 对象相等性判断和同一性判断

    在日常开发中经常需要编写代码比较不同的对象.例如,有时需要将对象都放到一个集合中,并编写代码对集合中的对象进行排序.搜索或者比较. System.Object类有两个Equals方法,如下: 1.实例 ...

  5. 读经典——《CLR via C#》(Jeffrey Richter著) 笔记_对象的相等性和同一性

    [重写Equals注意的事项] 1. Equals 必须是自反的:--x.Equals(x)肯定为 true 2. Equals 必须是对称的:--x.Equals(y)肯定返回与y.Equals(x ...

  6. Java中的equals和==的差别 以及Java中等价性和同一性的讨论

    ==对基本数据类型比较的是值,对引用类型比较的是地址 equals()比较的是对象的数据的引用 等价性原理: 自反性    x.equals(x)为true 对称性    x.equals(y) 为t ...

  7. CLR via C# 提纲

    第I部分 CLR基础第1章 CLR的执行模型 31.1 将源代码编译成托管模块 31.2 将托管模块合并成程序集 61.3 加载公共语言运行时 81.4 执行程序集的代码 101.4.1 IL和验证 ...

  8. CLR via C#深解笔记三 - 基元类型、引用类型和值类型 | 类型和成员基础 | 常量和字段

    编程语言的基元类型   某些数据类型如此常用,以至于许多编译器允许代码以简化的语法来操纵它们. System.Int32 a = new System.Int32();  // a = 0 a = 1 ...

  9. 第4章 类型基础 -- 4.1 所有类型都从System.Object派生

    4.1 所有类型都从System.Object派生 “运行时”要求每个类型最终都从System.Object类型派生. 由于所有类型最终都从System.Object派生,所以每个类型的每个对象都保证 ...

随机推荐

  1. vue + nginx部署404

    记录: vue项目打包放到服务器,使用nginx反向代理的时候路由页面刷新报404,因为刷新时,不存在路径对应的文件或文件夹,需要在nginx配置中添加代码: index index.html; tr ...

  2. H5微信授权登录

    这里介绍H5微信授权登录,采用了微信公众号授权原理,是oauth2的登录授权方式,简单的来讲,就是用户通过手机微信确认登录之后,微信方会返回一个授权码code给回第三方(接入方),这个授权码code一 ...

  3. linux c下的c文件 h文件 o文件 so文件 a文件 可执行文件 gcc使用

    linux下c语言工程: c文件:主要每个模块的原代码都在c文件中. h文件:每个c文件都跟着一个h文件,h文件的作用是放着c文件中函数的声明,结构体的定义,宏的定义等. o文件:目标文件.每个文件经 ...

  4. windows 10 安装openssh 0x800f0954 的一种解决方法

    安装与卸载参考:https://docs.microsoft.com/zh-cn/windows-server/administration/openssh/openssh_install_first ...

  5. 查看PL/SQL编译时的错误信息

    编译无效对象是DBA与数据库开发人员常见的工作之一.对于编译过程中的错误该如何去捕获,下面给出两种捕获错误的方法. 一.当前数据库版本信息及无效对象 1.查看当前数据库版本 [sql] view pl ...

  6. Django【第2篇】:Django之反向解析

    Django框架之第二篇 一.知识点回顾 1.MTV模型 model:模型,和数据库相关的 template:模板,存放html文件,模板语法(目的是将变量如何巧妙的嵌入到HTML页面中). view ...

  7. 【bzoj 4554】【Tjoi2016&Heoi2016】【NOIP2016模拟7.12】游戏

    题目 分析 当没有石头的时候,就用二分图匹配来做. 但现在加入了石头, 所以,求出每行和每列联通快的个数,如果有一块平地,包括在某个行联通块以及某个列联通块中,连边. //无聊打了网络流,匈牙利也可以 ...

  8. OmniGraffle 7使用的探索

    进去后可以将界面简化为4个主要区域:工具栏.工具栏.检查器和画布. 1.画布是在项目中创建.编辑和移动对象的地方 2.删除画布  选择编辑 画布删除画布 3.OmniGraffle项目至少需要一个画布 ...

  9. TweenMax的GSAP(GreenSock动画平台)GSAP,专业的Web动画库

    很好奇红框框里面的内容是什么,于是点了进去,又百度了下这个英文缩写具体指的什么东西. GSAP的全名是GreenSock Animation Platform,是一个从flash时代一直发展到今天的专 ...

  10. codeforces 868C - Qualification Rounds(构造)

    原题链接:http://codeforces.com/problemset/problem/868/C 题意:有k个队伍参加比赛,比赛有n个预选的题目,有些队伍对已经事先知道了一些题目.问能不能选出若 ...