一:对象相等性和同一性

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. SQL SERVER将多行数据合并成一行(转)

    1)比如表中有三列数据: 2)执行如下查询: 1 SELECT [USER_NAME], [USER_ACCOUNT] 2 , [ROLE_NAME] = stuff(( 3 SELECT ',' + ...

  2. 【转】linux 查看哪些进程用了swap

    转自:http://blog.csdn.net/xiangliangyu/article/details/8213127 如果系统的物理内存用光了,则会用到swap.系统就会跑得很慢,但仍能运行;如果 ...

  3. PCA 主成分分析

    链接1 链接2(原文地址) PCA的数学原理(转) PCA(Principal Component Analysis)是一种常用的数据分析方法.PCA通过线性变换将原始数据变换为一组各维度线性无关的表 ...

  4. L3-016. 二叉搜索树的结构

    二叉搜索树或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值:若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值:它的左.右子树也分别 ...

  5. linux shell 命令笔记

    标准输入.标准输出.标准错误 File descriptors are integers associated with an opened file or data stream. File des ...

  6. 构建游戏开发的大数据项目的流程demo图

  7. MySQL简版(二)

    第一章 表的约束 1.1 概念 对表中的数据进行限定,保证数据的正确性.有效性和完整性. 1.2 分类 主键约束:primary key. 非空约束:not null. 唯一约束:unique. 外键 ...

  8. 对postcss-plugin-px2rem的研究

    1.安装postcss-plugin-px2rem 2.配置 css: { loaderOptions: { postcss: { plugins: [ require('postcss-plugin ...

  9. js-将传来的数据排序,让(全部)这个小按钮小圈圈,始终排列在最前面

    let arryDemo=[]; for(var i=0;i<data.data.length;i++){ if(data.data[i].name=='全部'){ arryDemo.push( ...

  10. SQL把a表字段数据存到b表字段 update,,insert

    update SYS_Navigation set SYS_Navigation.PARENT_XH = SYS_Power_menu.parent_id,SYS_Navigation.web_tit ...