天天看點

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();
        }
    }
}
           

輸出結果:

CLR學習筆記--對象比較方法梳理

三、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();
        }
    }
}
           

輸出結果:

CLR學習筆記--對象比較方法梳理

四、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();
        }
    }
}
           

輸出結果:

CLR學習筆記--對象比較方法梳理

五、==和!= 運算符重載

“==”和“!=”對于引用類型和值類型比較方式主要有以下不同

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();
        }
    }
}
           

輸出結果:

CLR學習筆記--對象比較方法梳理

此外我們要注意上面一段在代碼在編譯時,編譯器提示有幾個警告:

CLR學習筆記--對象比較方法梳理

一個類型在重寫了Equals方法或者重載了運算符“==、!=“之後,之是以編譯器警告要同時定義GetHashCode,是因為在System.Collections.Hashtable類型、System.Collections.Genceric.Dictionary類型以及其他的一些集合中,要求兩個對象為了相等,必須具胡相同的哈稀碼,是以如果重寫了Equals,編譯器會提醒你,還應重寫GetHashCode,確定相等性算法和對象哈稀算法一緻,對于哈稀算法,在此篇不做讨論