CLR学习笔记--对象比较方法梳理

一、引言

对于对象的比较,C#提供了很多种比较方法,以及==和!=等运算来进行对象的同一性和相等性的比较,但我们在实际的编写代码中,可能会混淆各个方法的用法,在此废话不多说,我们一起来看些这几种方法的具体用法及含义。

二、Object. Equals (object obj)

这个方法是Object提供的虚方法,对于该方法,我们先区别引用类型和值类型的用法

对于引用类型,如果没有重写Equals,则默认比较对象的同一性,即比较引用地址,C#中大多数引用类型都重写了该方法,例如String类型比较的字符串是否相等

对于值类型,默认比较的是对象的相等性(即struct中所有字段的相等性)因为struct类型继承于System.ValueType而ValueType类中重写了Object中的Equals方法,代码具体实现可通过Reflector工具查看

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Demo
{
    public struct MyStruct
    {
        public int item;
        public MyStruct(int i)
        {
            item = i;
        }
    }
    public class MyClass
    {
        private int item;
        public int Item
        {
            get { return item; }
            set { item = value; }
        }
        public MyClass(int i)
        {
            item = i;
        }
        public MyClass() { }
    }
    public class MyNewClass
    {
        private int item1;
        public int Item1
        {
            get { return item1; }
            set { item1 = value; }
        }
        private float item2;
        public float Item2
        {
            get { return item2; }
            set { item2 = value; }
        }
        public MyNewClass(int i, float j)
        {
            item1 = i;
            item2 = j;
        }
        public MyNewClass() { }
        //重写了Object.Equals
        public override bool Equals(object obj)
        {
            if (obj == null) return false;
            MyNewClass o = obj as MyNewClass;
            if (o == null) return false;
            return item1 == o.item1 && item2 == o.item2;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            //没有重写Object.Equals的引用类型
            MyClass mc1 = new MyClass(1);
            MyClass mc2 = new MyClass(1);
            MyClass mc3 = mc2;
            //值类型
            MyStruct stc1 = new MyStruct(1);
            MyStruct stc2 = new MyStruct(1);
            MyStruct stc3 = new MyStruct(2);
            //重写了Object.Equals的引用类型
            MyNewClass nc1 = new MyNewClass(1, 1.0f);
            MyNewClass nc2 = new MyNewClass(1, 1.0f);
            MyNewClass nc3 = new MyNewClass(2, 1.0f);
            //比较没有重写Object.Equals的引用类型实例对象
            Console.WriteLine(mc1.Equals(mc2));//比较引用地址 返回False
            Console.WriteLine(mc2.Equals(mc3));//比较引用地址 返回True
            //比较值类型的实例对象
            Console.WriteLine(stc1.Equals(stc2));//ValueType重写了Equals方法,比较的是结构体中的所有字段 返回True
            Console.WriteLine(stc2.Equals(stc3));//比较结构体中的所有字段 返回False
            //比较重写了Object.Eqauls的引用类型实例对象
            //比较实例对象中所有字段的相等性
            Console.WriteLine(nc1.Equals(nc2));//比较对象中实例字段相等性 返回Ture
            Console.WriteLine(nc2.Equals(nc3));//返回False
            Console.Read();
        }
    }
}
输出结果:


三、Object. Equals (object objA,object objB)

这个方法是Object对象提供的静态方法

这个方法比较的过程是这样的:objA如果和objB引用相同实例或者两者同时为null时返回True,如果不满足前者情况,则返回objA.Equals(objB)的结果

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Demo
{
    public struct MyStruct
    {
        public int item;
        public MyStruct(int i)
        {
            item = i;
        }
    }
    public class MyClass
    {
        private int item;
        public int Item
        {
            get { return item; }
            set { item = value; }
        }
        public MyClass(int i)
        {
            item = i;
        }
        public MyClass() { }
    }
    public class MyNewClass
    {
        private int item1;
        public int Item1
        {
            get { return item1; }
            set { item1 = value; }
        }
        private float item2;
        public float Item2
        {
            get { return item2; }
            set { item2 = value; }
        }
        public MyNewClass(int i, float j)
        {
            item1 = i;
            item2 = j;
        }
        public MyNewClass() { }
        //重写了Object.Equals
        public override bool Equals(object obj)
        {
            if (obj == null) return false;
            MyNewClass o = obj as MyNewClass;
            if (o == null) return false;
            return item1 == o.item1 && item2 == o.item2;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {

            MyClass mc1 = new MyClass(1);
            MyClass mc2 = new MyClass(1);
            MyClass mc3 = mc2;

            MyStruct stc1 = new MyStruct(1);
            MyStruct stc2 = new MyStruct(1);
            object stc3 = stc2;

            MyNewClass nc1 = new MyNewClass(1, 1.0f);
            MyNewClass nc2 = new MyNewClass(1, 1.0f);

            Console.WriteLine(object.Equals(null, null));//两者都为null 返回True
            Console.WriteLine(object.Equals(mc1, mc2));//返回的是mc1.Equals(mc2)的结果 返回False
            Console.WriteLine(object.Equals(mc2, mc3));//返回的是mc2.Equals(mc3)的结果 返回True

            //对于值类型,调用object.Equals静态方法时,会发生装箱操作
            //实际比较时,返回是MyStruct结构体中重写的Equals方法
            Console.WriteLine(object.Equals(stc1, stc2));//返回的是stc1.Equals(stc2)的结果 返回True
            //stc2发生两次装箱过程
            //1、object stc3=stc2 stc2在GC堆上装箱,并将一个地址返回给stc3
            //2、调用object.Equals(stc2,stc3),stc2在GC又装箱一次,并将引用地址返回给第一个形参
            //两次装箱后,引用地址不同,实际比较时,返回的是MyStruct结构体中重写的Equals方法
            Console.WriteLine(object.Equals(stc2, stc3));//返回的是stc2.Equals(stc3)的结果,返回True

            Console.WriteLine(object.Equals(nc1, nc2));//返回的是nc1.Equals(nc2)的结果,返回True
            Console.Read();
        }
    }
}
输出结果:


四、Object. ReferenceEquals (object objA,object objB)

这个方法是Object对象提供的静态方法,该方法只比较objA和objB是否引用同一个对象实例,且该方法对值类型的比较永远返回False.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Demo
{
    public struct MyStruct
    {
        public int item;
        public MyStruct(int i)
        {
            item = i;
        }
    }
    public class MyClass
    {
        private int item;
        public int Item
        {
            get { return item; }
            set { item = value; }
        }
        public MyClass(int i)
        {
            item = i;
        }
        public MyClass() { }
    }
    class Program
    {
        static void Main(string[] args)
        {
            MyClass m1 = new MyClass(1);
            MyClass m2 = new MyClass(1);
            MyClass m3 = m2;

            MyStruct s1 = new MyStruct(1);
            MyStruct s2 = new MyStruct(1);
            MyStruct s3 = s2;

            Console.WriteLine(object.ReferenceEquals(m1, m2));//比较引用地址 返回False
            Console.WriteLine(object.ReferenceEquals(m2, m3));//比较引用地址 返回True

            Console.WriteLine(object.ReferenceEquals(s1, s2));//s1与s2装箱后引用的地址不一致,比较引用值永远返回False
            Console.WriteLine(object.ReferenceEquals(s3, s2));//返回False

            Console.Read();
        }
    }
}

输出结果:



五、==和!= 运算符重载

“==”和“!=”对于引用类型和值类型比较方式主要有以下不同

1、  对于引用类型,如果没有重载”==”和“!=”操作符的情况下,默认比较的是引用地址,很多.NET引用类型都重载了”==“和”!=“操作符,例如String类的”==“比较的是两个字符串是否相同

2、  对于.NET的基元值类型(short、Int16、Int32、Boolean等)在调用“==“和”!=”,CLR会实现内置的比较方式

3、  对于没有同重载“==”和“!=”的值类型,调用“==”和“!=”会引起编译器错误

通常我们设计一个类时,如果重写了Object.Equals(object o)时,一般也会重载“==“和”!=“操作符,并且重载“==”操作符调用重写的Equals方法.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Demo
{
    public struct MyStruct
    {
        public int item;
        public MyStruct(int i)
        {
            item = i;
        }
    }
    public struct MyNewStruct
    {
        public int item;
        public MyNewStruct(int i)
        {
            item = i;
        }
        public static bool operator ==(MyNewStruct m1, MyNewStruct m2)
        {
            return m1.Equals(m2);
        }
        public static bool operator !=(MyNewStruct m1, MyNewStruct m2)
        {
            return !m1.Equals(m2);
        }
    }
    public class MyClass
    {
        private int item;
        public int Item
        {
            get { return item; }
            set { item = value; }
        }
        public MyClass(int i)
        {
            item = i;
        }
        public MyClass() { }
    }
    public class MyNewClass
    {
        private int item;
        public int Item
        {
            get { return item; }
            set { item = value; }
        }
        public MyNewClass(int i)
        {
            item = i;
        }
        //重写了Object.Equals方法
        public override bool Equals(object obj)
        {
            if (obj == null) return false;
            if (this.GetType() != obj.GetType()) return false;
            MyNewClass m = obj as MyNewClass;
            return item.Equals(m.item);
        }
        //重载“==”操作符编译器要求同时必须要重载"!="
        public static bool operator ==(MyNewClass m1, MyNewClass m2)
        {
            return m1.Equals(m2);
        }
        public static bool operator !=(MyNewClass m1, MyNewClass m2)
        {
            return !m1.Equals(m2);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            //基元值类型
            Int32 i = 3;
            Int32 j = 3;
            //自定义值类型(没能重载”==”和“!=”)
            MyStruct s1 = new MyStruct(1);
            MyStruct s2 = new MyStruct(1);
            //自定义值类型(重载了“==”和“!=”)
            MyNewStruct ns1 = new MyNewStruct(1);
            MyNewStruct ns2 = new MyNewStruct(1);
            //没有重载“==”和“!=”的引用类型
            MyClass c1 = new MyClass(1);
            MyClass c2 = new MyClass(1);
            MyClass c3 = c2;
            //重载了“==”和”!=“的引用类型
            MyNewClass nc1 = new MyNewClass(1);
            MyNewClass nc2 = new MyNewClass(1);

            Console.WriteLine(i == j);//基元值类型==比较 返回True

            //自定义值类型(汉有重载==)调用==比较时,编译器提示错误
            //Console.WriteLine(s1 == s2);

            Console.WriteLine(ns1 == ns2);//自定义结构体重载了==操作符,返回 True

            Console.WriteLine(c1 == c2);//没有重载==操作符的引用类型比较,比较引用地址 返回 False
            Console.WriteLine(c2 == c3);//没有重载==操作符的引用类型比较,比较引用地址 返回 True

            Console.WriteLine(nc1 == nc2);//重载了==操作符的引用类型比较,调用nc1.Equals(nc2) 返回True
            Console.Read();
        }
    }
}
输出结果:


此外我们要注意上面一段在代码在编译时,编译器提示有几个警告:


一个类型在重写了Equals方法或者重载了运算符“==、!=“之后,之所以编译器警告要同时定义GetHashCode,是因为在System.Collections.Hashtable类型、System.Collections.Genceric.Dictionary类型以及其他的一些集合中,要求两个对象为了相等,必须具胡相同的哈稀码,所以如果重写了Equals,编译器会提醒你,还应重写GetHashCode,确保相等性算法和对象哈稀算法一致,对于哈稀算法,在此篇不做讨论


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值