C#中Linq的去重方式Distinct詳解

【君莫笑】發表於2024-05-10
一、首先建立一個控制檯應用程式,新增一個Person物件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Compare
{
    public class Person
    {
        public string Name { get; set; }
 
        public int Age { get; set; }
 
        public Person(string name, int age)
        {
            this.Name = name;
            this.Age = age;
        }
    }
}
二、建立測試資料
建立了一個Name="ZhangSan"的Person物件,放入personList兩次,然後personList又建立了幾個Person物件,這幾個Person物件中也有Name、Age都重複的。例如:"XiaoMing",26.
Person person = new Person("ZhangSan",26);
List<Person> personList = new List<Person>() {
    person,
    new Person("XiaoMing",25),
    new Person("CuiYanWei",25),
    new Person("XiaoMing",26),
     new Person("XiaoMing",25),
    new Person("LaoWang",26),
    new Person("XiaoMing",26),
    person
};
三、測試
下面的程式碼中用了兩種方式來選擇不重複的資料。
List<Person> defaultDistinctPersons = personList.Distinct().ToList<Person>();
foreach (Person p in defaultDistinctPersons)
{
    Console.WriteLine("Name:{0}    Age:{1}",p.Name,p.Age);
}
Console.WriteLine("-----------------------------------------------------");
List<Person> comparePersons = personList.Distinct(new PersonCompare()).ToList<Person>();
foreach (Person p in comparePersons)
{
    Console.WriteLine("Name:{0}    Age:{1}", p.Name, p.Age);
}
Console.ReadLine();
在華麗分割線上面是使用預設的distinct,下面是透過整合IEqualityComparer介面。下面是實現介面的程式碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Compare
{
    public class PersonCompare:IEqualityComparer<Person>
    {
        public bool Equals(Person x, Person y)
        {
            if (x == null || y == null)
                return false;
            return x.Name.Equals(y.Name) && x.Age == y.Age;
        }
        public int GetHashCode(Person obj)
        {
            return obj.GetHashCode();
        }
    }
}
在上面的程式碼中,繼承IEqualityComparer介面,主要是實現了兩個方法:bool Equals(T x, T y);int GetHashCode(T obj);可能即使實現了介面也不瞭解裡面是怎麼個原理,我們先看下執行結果。

從上面的執行結果可以看到,兩個執行結果是一樣的,還是有重複的資料:例如XiaoMing,26.兩個都沒去除重複,只有ZhangSan那兩個去除重複了。是不是有實現介面多此一舉的感覺。那為什麼還要有這個介面還要實現它呢?其實要說下GetHashCode和Equals。

在說GetHashCode和Equals之前先了解下distinct(),這個方法Distinct 預設比較的是物件的引用,所以使用預設的distinct()方法是ZhangSan物件是過濾除去的,而XiaoMing,26是兩個不同的物件,沒有除去。

然後說下GetHashCode和Equals兩個方法.

1.雜湊碼雜湊程式碼是一個用於在相等測試過程中標識物件的數值。它還可以作為一個集合中的物件的索引。如果兩個物件的 Equals 比較結果相等,則每個物件的 GetHashCode 方法都必須返回同一個值。 如果兩個物件的比較結果不相等,這兩個物件的 GetHashCode 方法不一定返回不同的值.
簡而言之,如果你發現兩個物件 GetHashCode() 的返回值相等,那麼這兩個物件就很可能是同一個物件;但如果返回值不相等,這兩個物件一定不是同一個物件.

當GetHashCode可以直接分辨出不相等時,Equals就沒必要呼叫了,而當GetHashCode返回相同結果時,Equals方法會被呼叫從而確保判斷物件是否真的相等。所以,還是那句話:GetHashCode沒必要一定把物件分辨得很清楚(況且它也不可能,一個int不可能代表所有的可能出現的值),有Equals在後面做保障。GetHashCode僅需要對物件進行快速判斷。

上面的幾句算是總結性的說明了兩個方法的是怎麼個路子,這也能解釋出ZhangSan的重複去除,而其他的幾個物件沒有去重複的原因,ZhangSan那是一個物件,其他的雖然Name、Age相等,但不是同一個物件。

我們可以稍微改動下程式碼來驗證上面的語句.在實現IEqualityComparer的介面類中列印出一些資訊就能看明白
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Compare
{
    public class PersonCompare:IEqualityComparer<Person>
    {
        public bool Equals(Person x, Person y)
        {
            if (x == null || y == null)
                return false;
            Console.WriteLine("XName:{0} XAge:{1} XHashCode:{2}  YName:{3} YAge:{4} YHashCode:{5}", x.Name, x.Age, x.GetHashCode(),y.Name,y.Age,y.GetHashCode());
            return x.Name.Equals(y.Name) && x.Age == y.Age;
        }
        public int GetHashCode(Person obj)
        {
            Console.WriteLine("GetHashCode Name:{0} Age:{1} HashCode:{2}",obj.Name,obj.Age,obj.GetHashCode());
            return obj.GetHashCode();
        }
    }
}

在GetHashCode中列印了物件的Name、Age和HashCode。可以看到HashCode只有ZhangSan的是相同的,在Equals方法中只列印出了ZhangSan的,還是因為上面的先判斷HashCode,相等了再使用Equals判斷。

我們再改動下實現IEqualityComparer的介面類
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Compare
{
    public class PersonCompare:IEqualityComparer<Person>
    {
        public bool Equals(Person x, Person y)
        {
            if (x == null || y == null)
                return false;
            Console.WriteLine("XName:{0} XAge:{1} XHashCode:{2}  YName:{3} YAge:{4} YHashCode:{5}", x.Name, x.Age, x.GetHashCode(), y.Name, y.Age, y.GetHashCode());
            return x.Name.Equals(y.Name) && x.Age == y.Age;
        }
        public int GetHashCode(Person obj)
        {
            //Console.WriteLine("GetHashCode Name:{0} Age:{1} HashCode:{2}",obj.Name,obj.Age,obj.GetHashCode());
            //return obj.GetHashCode();
            string s = string.Format("{0}_{1}",obj.Name,obj.Age);
            Console.WriteLine("Name:{0} Age:{1} HashCode:{2}",obj.Name,obj.Age, s.GetHashCode());
            return s.GetHashCode();
        }
    }
}

根據上面的的程式碼和測試結果我們可以看到,GetHashCode執行了7次(7個物件),Equals執行了3次,因為ZhangSan,26和XiaoMing,25兩個的雜湊碼是一樣的就沒有繼續往下執行。
來源:https://www.jb51.net/article/254515.htm

相關文章