簡體   English   中英

將EqualityComparer <T>優先於IEqualityComparer <T>

[英]Preferring EqualityComparer<T> to IEqualityComparer<T>

從MSDN上的IEqualityComparer<T>備注部分:

  1. 我們建議您從EqualityComparer <T>類派生而不是實現IEqualityComparer <T>接口,因為EqualityComparer <T>類使用IEquatable <T> .Equals方法而不是Object.Equals方法測試相等性。 ...

    • 我不明白引用的論點,為什么我們寧願從EqualityComparer<T>類派生而不是實現IEqualityComparer<T> 它意味着實現IEqualityComparer<T>對象將使用Object.Equals測試相等性,但是當我們不想使用Object.EqualsIEquatable<T>.Equals來測試相等性時,它不是實現IEqualityComparer<T>的全部要點。 IEquatable<T>.Equals

    • 它還暗示如果我們從EqualityComparer<T>派生,那么派生類將使用IEquatable<T>.Equals方法測試相等性。 同樣,當我們不想使用Object.EqualsIEquatable<T>.Equals測試相等性時,不是從EqualityComparer<T>派生的全部要點(因為EqualityComparer<T>.Default已經使用Object.Equals測試了或IEquatable<T>.Equals )?

  2. ...這與Dictionary <TKey,TValue>類和其他泛型集合的Contains,IndexOf,LastIndexOf和Remove方法一致。

    • 我假設.NET庫中的大多數集合測試默認的元素相等(即當用戶不通過調用IEquatable<T>.EqualsObject.Equals取決於他們自己的自定義IEqualityComparer<T>對象)(取決於是否通過EqualityComparer<T>.Default實現類型T元素實現IEquatable<T>

    • 為什么這些集合(在測試默認相等時)不直接調用IEquatable<T>.EqualsObject.Equals而不是通過EqualityComparer<T>.Default類?

關於你的第一個問題:

IEqualityComparer<T>類的備注部分似乎並沒有提供理由為什么你更喜歡從界面上的抽象類派生,這聽起來更像是為什么首先存在相等比較器接口的原因。 它說的實際上沒用,它基本上描述了默認實現的作用。 如果有的話,他們在這里提供的“推理”聽起來更像是比較器可以做什么的指導方針,與實際做的無關。

查看EqualityComparer<T>類的公共/受保護接口,只有一種兌換質量,它實現了非通用的IEqualityComparer接口。 我認為他們的意思是說他們建議從中派生,因為EqualityComparer<T>實際上實現了非通用的IEqualityComparer接口,這種方式可以在需要非泛型比較器的地方使用。

它在IComparer<T>的備注部分中更有意義:

我們建議您從Comparer<T>類派生而不是實現IComparer<T>接口,因為Comparer<T>類提供IComparer.Compare方法的顯式接口實現和獲取默認比較器的Default屬性物體。

我懷疑應該對IEqualityComparer<T>說些類似的東西,但有些想法混淆了,結果卻是一個不完整的描述。


關於你的第二個問題:

在圖書館中找到的館藏的主要目標是盡可能靈活。 一種方法是通過提供IComparer<T>IEqualityComparer<T>進行比較,允許自定義比較其中對象的方法。 如果沒有提供默認比較器的實例,那么獲取默認比較器的實例比直接進行比較要容易得多。 反過來,這些比較器可能包含調用恰當比較的必要邏輯。

例如,默認比較器可以確定T是否實現IEquatable<T>並在對象上調用IEquatable<T>.Equals或以其他方式使用Object.Equals 更好地封裝在比較器中,而不是在集合代碼中可能重復。

此外,如果他們想要直接調用IEquatable<T>.Equals ,他們將不得不在T上添加一個約束,使這個調用成為可能。 這樣做會使其靈活性降低,並且不利於首先提供比較器的好處。

我不明白1的建議。這對我來說似乎很奇怪。

對於2 - 通常,您最終得到一個具有IEqualityComparer<T>的類型(如Dictionary )。 雖然實現可以存儲一個空值並且顯式地調用Equals本身,但這樣做會很痛苦 - 並且還會涉及到顯着的丑陋以確保它不會不必要地實現IEquatable<T>盒值類型。 使用該接口, EqualityComparer<T>.Default 非常簡單且更加一致。

從基類派生類的主要原因是基類可以提供可以重用的代碼,因此您不必自己編寫代碼。

如果你從界面派生你的比較器,你必須創建自己給你一個默認比較器的代碼(當然只有你需要它,但是,嘿,每個人都想要免費的功能!)

Class EqualityComparer使用工廠設計模式

在Factory模式中,我們創建對象而不將創建邏輯暴露給客戶端,並使用通用接口引用新創建的對象。

好消息是,EqualityComparer的所有用戶只需要調用prperty default,並為他們完成所有操作以創建暴露接口IEqualtiyComparer的正確對象。

這樣做的好處是,如果你需要IEqualityComparer作為函數中的參數,那么你不必檢查類T是否實現了IEqualtiy<T> ,字典為你做了。

如果您從EqualtityComparer<T>派生並確保派生類遵循工廠設計模式,那么在幾個相等的比較器之間切換很容易。

此外,與任何工廠一樣,您只需更改工廠的參數即可生成完全不同的等式比較器。

當然你可以在不派生EqualtyComparer<T>情況下創建一個相等比較器工廠,但是如果你派生你的工廠可以創建一個額外類型的相等比較器:默認的相等比較器,它是使用IEquatable<T>那個或者的Object.Equals。 你不必為此編寫任何額外的代碼,只需派生!

您是否覺得從EqualtyComparer派生它是否有用,取決於您是否認為工廠設計模式是有用的。

例如,假設您要檢查兩個字典是否相等。 人們可以想到幾個平等程度:

  1. 字典X和Y如果它們是同一個對象則相等
  2. 如果X和Y具有相等的鍵(使用字典鍵比較器),並且它們的值是同一個對象,則它們相等
  3. 如果X和Y具有相等的鍵(使用字典鍵比較器),並且如果它們的值相等則使用TValue的默認相等比較器,則它們相等
  4. 如果它們具有相等的鍵(使用字典鍵比較器),則X和Y相等,並且使用提供的值的相等比較器使用相等的值。

如果從EqualityComparer派生字典比較器類,則已經有comparer(1)。 如果提供的TValue比較器是從EqualityComparer派生的,則(3)和(4)之間沒有真正的區別。

因此,讓我們派生出可以創建這四個比較器的工廠:

class DictionaryComparerFactory<TKey, TValue> : 
    EqualitiyComparer<Dictionary<TKey, TValue>>
{
    // By deriving from EqaulityComparer, you already have comparer (1)
    // via property Default

    // comparer (4):
    // X and Y are equal if equal keys and equal values using provided value comparer
    public static IEqualityComparer<Dictionary<TKey, TValue>>
        CreateContentComparer(IEqualityComparer<TValue> valueComparer)
    {
        return new DictionaryComparer<TKey, TValue>(valueComparer);
    }

    // comparer (3): X and Y equal if equal keys and values default equal
    // use (4) by providing the default TValue comparer
    public static IEqualityComparer<Dictionary<TKey, TValue>>
        CreateDefaultValueComparer(IEqualityComparer<TValue> valueComparer)
    {
        IEqualityComparer<TValue> defaultValueComparer =
            EqualtiyComparer<TValue>.Default;
        return new DictionaryComparer<TKey, TValue>(defaultValuecomparer);
    }

    // comparer (2): X and Y are equal if equal keys and values are same object
    // use reference equal for values
    public IEqualityComparer<TKey, TValue> CreateReferenceValueComparer()
    {
        IEqualityComparer<TValue> referenceValueComparer = ...
        return new DictionaryComparer<TKey, TValue>(referenceValuecomparer);
    }
}

對於comparer(2),您可以使用參考值比較器,如使用ReferenceEquals的 stackoverflow IEqualityComparer中所述

所以現在我們有四個不同的相等比較器,只提供一個比較器的代碼。 其余的重復使用!

沒有創建默認比較器的工廠,這種重用就不那么容易了

比較器代碼(4):使用提供的比較器檢查TValue的相等性

// constructor
protected DictionaryComparer(IEqualityComparer<TValue> valueComparer) : base()
{   // if no comparer provided, use the default comparer
    if (Object.ReferenceEquals(valueComparer, null))
        this.valueComparer = EqualityComparer<TValue>.Default;
    else
        this.valueComparer = valueComparer
}

// comparer for TValue initialized in constructor
protected readonly IEqualityComparer<TValue> valueComparer;

public override bool Equals(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y)
{
    if (x == null) { return y == null; } 
    if (y == null) return false;
    if (Object.ReferenceEquals(x, y)) return true;
    if (x.GetType() != y.GetType()) return false;

    // now do equality checks according to (4)
    foreach (KeyValuePair<TKey, TValue> xKeyValuePair in x)
    {
        TValue yValue;
        if (y.TryGetValue(xKeyValuePair.Key, out yValue))
        {   // y also has x.Key. Are values equal?
            if (!this.valueComparer.Equals(xKeyValuePair.Value, yValue))
            {   // values are not equal
                return false;
            }
            // else: values equal, continue with next key
        }
        else
        {   // y misses a key that is in x
            return false;
        }
    }

    // if here, all key/values equal
    return true;
}

現在我們可以簡單地使用不同的比較器比較兩個詞典:

var dictionaryX = ...
var dictionaryY = ...

var valueComparer1 = ...
var valueComparer2 = ...

var equalityComparer1 = DictionaryComparer<...>.Default();
var equalityComparer2 = DictionaryComparer<...>..CreateDefaultValueComparer();
var equalityComparer3 = DictionaryComparer<...>.CreatereferenceValueComparer();
var equalityComparer4 = DictionaryComparer<...>
   .CreateContentComparer(valueCompaerer1);
var equalityComparer5 = DictionaryComparer<...>
   .CreateContentComparer(valueCompaerer2);

因此推導導致我的相等比較器工廠總是具有適當的Defautlt比較器。 讓我自己編寫代碼

如果您在MSDN解釋中更改了一個單詞,即 使用中獲得它會更有意義。

在MSDN上: EqualityComparer<T>

我們建議您(不是派生自己) 使用 EqualityComparer<T>類而不是實現IEqualityComparer<T>接口,因為EqualityComparer<T>類使用IEquatable<T>.Equals方法而不是Object.Equals來測試相等性Object.Equals方法。 這與Dictionary類和其他泛型集合的ContainsIndexOfLastIndexOfRemove方法一致。

當然,這僅在T實現IEquality<T>

請注意,奇怪的是,只有ArrayList<T>具有IndexOfLastIndexOf方法,並且沒有任何重載可以使IEqualityComparer<T>用於任何方法。 其他泛型集合的構造函數采用IEqualityComparer<T>

在MSDN上: Comparer<T>:

我們建議您(不是派生自) 使用 Comparer<T>類而不是實現IComparer<T>接口,因為Comparer<T>類提供IComparer.Compare方法的顯式接口實現和Default屬性獲取對象的默認比較器。

當然,這只有在T實現IComparableIComparable<T>時才有效

如果T沒有實現從EqualityComparer<T>Comparer<T>派生的所需接口是有用的,因為它為免費提供非泛型接口的實現。

另一方面,實現IEqualityComparer<T>IComparer<T>可以獲得性能優勢,因為它可以跳過對IEquatable<T>IComparable<T>的調用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM