1. C#的值类型和引用类型

  C#的对象里面有两种类型,一个是引用类型,一个是值类型,值类型和引用类型的具体分类可以看下面的分类。
 
  在C#中,不管是引用类型还是值类型,他们都隐式继承Object类(这里应该这样仔细理解,引用类型是直接地继承Object类,但是值类型是继承的是Object类的子类ValueType。
  需要注意的是,C#的对象内存管理方式和C++或者Java都是有非常的不同的,简单来说,C#的内存组织方式是通过栈堆和托管堆的方式组织的。也就是下面这个图展示的那样:
  
 
 
2. Object.Equals方法:

  Equal方法,顾名思义,就是一个辨别两个对象是否是一个对象的方法。
  在C#中,对于引用类型,其实就是比较两个地址是否是相等的(也就是Object.ReferenceEquals是一样的)。
 
  MSDN中对于怎么写引用类型的Eqaul方法给出了下面的建议:
  • Consider overriding Equals if the semantics of the type are based on the fact that the type represents some value(s).
  (当你要重载引用形式的equal的时候,你要比较的两个值得语义必须是引用类型)
  • Most reference types must not overload the equality operator, even if they override Equals. However, if you are implementing a reference type that is intended to have value semantics, such as a complex number type, you must override the equality operator.
  (当你重载了一个引用类型的equal方法的时候,一定不要重载相等运算符==;当你重载的是值类型的equal方法时候,必须重载相等运算符。)
  • You should not override Equals on a mutable reference type. This is because overriding Equals requires that you also override the GetHashCode method, as discussed in the previous section. This means that the hash code of an instance of a mutable reference type can change during its lifetime, which can cause the object to be lost in a hash table.
  (※不能把Equal方法重载到一个地址可变的量上(因为重载了equal函数我们必须重载GetHashCode方法),一个实例可变引用的散列值会随着他的生存期会一直改变,这会导致我们无法在散列表中找到这个对象。)
 
  关于值类型的Equal方法,MSDN给出了下面的建议:
  • If you are defining a value type that includes one or more fields whose values are reference types, you should override Equals(Object). The Equals(Object) implementation provided by ValueType performs a byte-by-byte comparison for value types whose fields are all value types, but it uses reflection to perform a field-by-field comparison of value types whose fields include reference types.
  (如果你自定义了一个值类型,且这个值类型含有引用类型,那么你必须重载Equal方法,在值类型中,Equal方法对于值类型中的值类型的比较是值与值之间的比较(内存中byte o byte),引用类型比较的是两个对象是否指向同一个内存地址)
  • If you override Equals and your development language supports operator overloading, you must overload the equality operator.
  (如果你重载了值类型的Equal方法,那么你一定要重载相等运算符)
  (如果你重载了值类型Equal方法,推荐同时实现IEquatable<T> 接口,比较时使用IEquatable<T> 接口而不是把实例封箱到Object中)。
  (值类型的重载也是要重载GetHashCode的。)
 
  MSDN文档告诉我们当我们无论是写一个值类型还是引用类型的集合,如果要写Equals方法,都要遵循下列规则:
  1. x.Equals(x) returns true. This is called the reflexive property.(自己等于自己为真)
  2. x.Equals(y) returns the same value as y.Equals(x). This is called the symmetric property.(可交换性)
  3. if (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true. This is called the transitive property.(可传递性)
  4. Successive invocations of x.Equals(y) return the same value as long as the objects referenced by x and y are not modified.(与引用无关)
  5. x.Equals(null) returns false. However, null.Equals(null) throws an exception; it does not obey rule number two above.(与null进行比较返回假)
  struct结构隐式继承 System.ValueType,这个类已经重写了Object.Equals(Object)方法,但是这个重写过的方法是用的反射技术来检测所有的public和非public属性,尽管这个操作在struct类型里面用是正确的,但是这个操作是很慢的。换句话说,如果我们想自定义自己的值类型,最好是从class继承并改写Equals方法。
 
3. 实现例子:

  1. 实现引用类型的Equals:
 
  对于引用类型来说,本身是很简单的,因为Object.ReferenceEquals就是一个判断两个对象是否是同一个引用的最直接的方法。但是需要注意的是,如果使用Object.ReferenceEquals判断两个值类型时,将永远返回false。
  但是对于字符串常量来说有个很特别的地方就是,我们知道字符串在常量都是放在程序的常量区(data段),所以如果写出以下代码:
string str1 = "Fuck";
string str2 = "Fuck";
  Object.ReferenceEquals(str1,str2)将会返回真。
 
  2. 实现值类型的Equals:
  • Override the virtualObject.Equals(Object) method. In most cases, your implementation of bool Equals( object obj ) should just call into the type-specific Equals method that is the implementation of the System.IEquatable<T> interface. (See step 2.)

   (重写Equals方法或者实现System.IEquatable<T>接口)

  • Implement the System.IEquatable<T> interface by providing a type-specific Equals method. This is where the actual equivalence comparison is performed. For example, you might decide to define equality by comparing only one or two fields in your type. Do not throw exceptions from Equals. For classes only: This method should examine only fields that are declared in the class. It should call base.Equals to examine fields that are in the base class. (Do not do this if the type inherits directly from Object, because the Object implementation of Object.Equals(Object) performs a reference equality check.)

   (System.IEquatable<T>接口里面可以仅实现部分比较,但绝对不要在Equals里面抛出异常,如果比较的对象有基类,必须调用base.Equals方法)

  • Optional but recommended: Overload the == and != operators.

   (最好重载==和!=运算符(同时实现))

  • Override Object.GetHashCode so that two objects that have value equality produce the same hash code.
   (必须重写Object.GetHashCode)
  • Optional: To support definitions for "greater than" or "less than," implement the IComparable<T> interface for your type, and also overload the <= and >= operators.
   (大于小于号这些可选,比如复数类的时候就需要这些运算符重载)。
  
  我们如果想希望我们的Class实现值类型,那么有两个办法,第一个办法比较简单粗暴,直接重写Equal和GetHashCode,一定要记住要重写GetHashCode,比如我们可以写出下面的代码。
class Test1
{
  public string Name { get; set; }   public Test1(string name)
  {
    Name = name;
  }   public override bool Equals(object obj)
  {
    if (obj == null || obj.GetType() != typeof(Test1))
    return false;     Test1 testObj = obj as Test1;     return this.Name == testObj.Name;
  }   public static bool operator==(Test1 t1, Test1 t2)
  {
    return t1.Equals(t2);
  }   public static bool operator !=(Test1 t1, Test1 t2)
  {
    return !(t1 == t2);
  }   public override int GetHashCode()
  {
    return base.GetHashCode();
  }
}
这个时候我们可以把Test1来进行值的比较了,但是上面的代码并没有真正重载GetHashCode,以至于我们会导致下面的问题,我们写入下面的代码:
class Program
{
static void Main(string[] args)
{
Test1 t1 = new Test1("HaHa");
  Test1 t2 = new Test1("HaHa");     Console.WriteLine("t1.Equals(t2)? {0}", t1.Equals(t2));
    Console.WriteLine("t1 == t2? {0}", t1 == t2);     List<Test1> list = new List<Test1>();
    list.Add(t1);     Console.WriteLine("list contains Test1(HaHa)? {0}", list.Contains(t2));
    Console.ReadKey();
  }
}

  这个时候看上去还很正常,但是如果我们加一点测试:
class Program
{
  static void Main(string[] args)
  {
    Test1 t1 = new Test1("HaHa");
    Test1 t2 = new Test1("HaHa");     Console.WriteLine("t1.Equals(t2)? {0}", t1.Equals(t2));
    Console.WriteLine("t1 == t2? {0}", t1 == t2);     ICollection<Test1> list = new HashSet<Test1>();
    list.Add(t1);     Console.WriteLine("list contains Test1(HaHa)? {0}", list.Contains(t2));     Dictionary<Test1, int> dict = new Dictionary<Test1, int>();
    dict.Add(t1, );     Console.WriteLine("dict contains Test1(HaHa)? {0}", dict.Keys.Contains(t2));
    Console.ReadKey();
  }
}

  这就出现了很奇怪的现象了,我们已经假定了我们的Test1是一个值类型了,我们如果把这个东西作为HashSet的类型或者Dictionary的键值的时候,却发现了这样的诡异的情况,我们认为相等的值不再相等。
  这种情况的原因就是因为我们没有真正重写GetHashCode,如果把GetHashCode的代码改成这样:
public override int GetHashCode()
{
return Name.GetHashCode();
}
  这样改动以后我们的程序将会出现正确的结果:
  
 
  当然了正如我们上面所说的,我们可以通过实现IEqualable<T>的泛型接口来实现强类型的比较:
class Test : IEquatable<Test>
{
  public Test(string name)
  {
    Name = name;
  }   public bool Equals(Test other)
  {
    return this.Name == other.Name;
  }   public override bool Equals(object obj)
  {
    return base.Equals(obj);
  }   public override int GetHashCode()
  {
    return Name.GetHashCode();
  }   public string Name { get; set; }
}
  如果实现了IEqualable<T>的泛型接口,那么Equals就没必要重载了,这就是我们经常使用的StringBuilder的是实现方式(不过StringBuilder并没有实现IEqualable<T>这个接口,而是直接重载了Equals这个方法)。
 
  如果把class改为struct,因为值类型不可能为null,所以可以这样写:
struct Test1
{
  public string Name { get; set; }   public Test1(string name)
  {
    Name = name;
  }   public override bool Equals(object obj)
  {
    if (obj is Test1)
    {
      return this.Name == ((Test1)obj).Name;
    }
    return false;
  }   public static bool operator==(Test1 t1, Test1 t2)
  {
    return t1.Equals(t2);
  }     public static bool operator!=(Test1 t1, Test1 t2)
  {
    return !(t1.Equals(t2));
  }   public override int GetHashCode()
  {
    return Name.GetHashCode();
  }
}
 

C#值类型和引用类型与Equals方法的更多相关文章

  1. [c#基础]值类型和引用类型的Equals,==的区别

    引言 最近一个朋友正在找工作,他说在笔试题中遇到Equals和==有什么区别的题,当时跟他说如果是值类型的,它们没有区别,如果是引用类型的有区别,但string类型除外.为了证实自己的说法,也研究了一 ...

  2. C#类和接口、虚方法和抽象方法及值类型和引用类型的区别

    1.C#类和接口的区别接口是负责功能的定义,项目中通过接口来规范类,操作类以及抽象类的概念!而类是负责功能的具体实现!在类中也有抽象类的定义,抽象类与接口的区别在于:抽象类是一个不完全的类,类里面有抽 ...

  3. .net学习之.net和C#关系、运行过程、数据类型、类型转换、值类型和引用类型、数组以及方法参数等

    1..net 和 C# 的关系.net 是一个平台,C#是种语言,C#语言可以通过.net平台来编写.部署.运行.net应用程序,C#通过.net平台开发.net应用程序2..net平台的重要组成FC ...

  4. ==和Equals与值类型和引用类型

    ==和Equals 对于值类型来说判断的是值,对于引用类型来说判断的是堆地址 注意:string 是引用类型(也可看做只读char[]数组)(字符串的不可变性·拘留池)特殊的值类型(使用==.Equa ...

  5. 从CLR角度来看值类型与引用类型

    前言 本文中大部分示例代码来自于<CLR via C# Edition3>,并在此之上加以总结和简化,文中只是重点介绍几个比较有共性的问题,对一些细节不会做过深入的讲解. 前几天一直忙着翻 ...

  6. 学习记录 java 值类型和引用类型的知识

    1. Java中值类型和引用类型的不同? [定义] 引用类型表示你操作的数据是同一个,也就是说当你传一个参数给另一个方法时,你在另一个方法中改变这个变量的值, 那么调用这个方法是传入的变量的值也将改变 ...

  7. Windows Phone 开发起步之旅之二 C#中的值类型和引用类型

    今天和大家分享下本人也说不清楚的一个C#基础知识,我说不清楚,所以我才想把它总结一下,以帮助我自己理解这个知识上的盲点,顺便也和同我一样不是很清楚的人一起学习下.  一说起来C#中的数据类型有哪些,大 ...

  8. 值类型与引用类型(特殊的string) Typeof和GetType() 静态和非静态使用 参数传递 相关知识

    学习大神博客链接: http://www.cnblogs.com/zhili/category/421637.html 一 值类型与引用类型 需要注意的string 是特殊类型的引用类型. 使用方法: ...

  9. C#基础(六)——值类型与引用类型

    CLR支持两种类型:值类型和引用类型. 值类型包括C#的基本类型(用关键字int.char.float等来声明),结构(用struct关键字声明的类型),枚举(用enum关键字声明的类型):而引用类型 ...

随机推荐

  1. Lightoj1037【状压DP】

    题意: 给出n个怪的生命值,然后n个怪手里有一把枪,给出n*n的矩阵代表第i个怪对第j个怪的伤害值: 现在让你去干掉n个怪,只能平A使怪扣一滴血,干掉目标后, 可以把这个目标的武器拿进口袋然后用这个武 ...

  2. unity关于StartCoroutine的简单线程使用

    StartCoroutine在unity3d的帮助中叫做协程,意思就是启动一个辅助的线程. 在C#中直接有Thread这个线程,但是在unity中有些元素是不能操作的.这个时候可以使用协程来完成. 使 ...

  3. Node.js 内置模块fs(文件系统)

    fs模块的三个常用方法 1.fs.readFile() -- 读文件 2.fs.writeFile() -- 写文件 3.fa.stat() -- 查看文件信息 fs模块不同于其它模块的地方是它有异步 ...

  4. LeetCode.897-递增搜索树(Increasing Order Search Tree)

    这是悦乐书的第346次更新,第370篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第211题(顺位题号是897).给定一棵树,按中序遍历顺序重新排列树,以便树中最左边的节 ...

  5. MQ简介1

    站在巨人的肩膀上 关于消息队列的使用 一.消息队列概述消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构.目前使用较多的消息 ...

  6. C 语言实例 - 将字符串写入文件

    C 语言实例 - 将字符串写入文件 C 语言实例 C 语言实例 将字符串写入文件. 实例 #include <stdio.h> #include <stdlib.h> /* e ...

  7. typescript学习笔记(三)---接口

    关于第二章的学习笔记是变量声明. 接口:TypeScript的核心原则之一是对值所具有的结构进行类型检查. 它有时被称做“鸭式辨型法”或“结构性子类型化”. 在TypeScript里,接口的作用就是为 ...

  8. 基于java开发的在线题库系统tamguo

    简介 探果网(简称tamguo)是基于java开发的在线题库系统,包括 在线访问 后台运营 会员中心 书籍中心 管理员账号:system 密码:123456 因为线上数据和测试数据没有做到隔离,作者已 ...

  9. PostgreSQL - 用psql 运行SQL文件

    对于预先写好的SQL文件,比如/home/user1/updateMyData.sql, 可以有两种方式来运行这个SQL文件. 方式一:连接db后执行SQL文件 首先通过psql连接到对应的db: p ...

  10. Redis - Windows平台下怎么切换db并且清理数据

    Redis 本身支持16个数据库(0~15),通过 数据库id 设置,默认为0.在Windows平台下可以通过启动redis-cli.exe来进入客户端,客户端默认连接数据库0,在客户端里可以输入各种 ...