ห่อผู้รับมอบสิทธิ์ใน IEqualityComparer


127

Linq หลายฟังก์ชั่นที่คำนวณได้ใช้เวลาIEqualityComparer<T>. มีคลาส Wrapper ที่สะดวกในการปรับ a delegate(T,T)=>boolเพื่อใช้งานIEqualityComparer<T>หรือไม่? ง่ายพอที่จะเขียน (หากคุณเพิกเฉยปัญหาเกี่ยวกับการกำหนดแฮชโค้ดที่ถูกต้อง) แต่ฉันต้องการทราบว่ามีวิธีแก้ปัญหาแบบสำเร็จรูปหรือไม่

โดยเฉพาะอย่างยิ่งฉันต้องการตั้งค่าการดำเนินการบนDictionarys โดยใช้เฉพาะคีย์เพื่อกำหนดความเป็นสมาชิก (ในขณะที่รักษาค่าตามกฎที่แตกต่างกัน)

คำตอบ:


44

โดยปกติฉันจะได้รับการแก้ไขโดยการแสดงความคิดเห็น @Sam ในคำตอบ (ฉันได้ทำการแก้ไขบางส่วนในโพสต์ต้นฉบับเพื่อล้างข้อมูลเล็กน้อยโดยไม่ต้องแก้ไขพฤติกรรม)

ต่อไปนี้เป็นคำตอบของ@ Samโดยมีการแก้ไขที่สำคัญของ [IMNSHO] สำหรับนโยบายการแฮชเริ่มต้น: -

class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    readonly Func<T, T, bool> _comparer;
    readonly Func<T, int> _hash;

    public FuncEqualityComparer( Func<T, T, bool> comparer )
        : this( comparer, t => 0 ) // NB Cannot assume anything about how e.g., t.GetHashCode() interacts with the comparer's behavior
    {
    }

    public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
    {
        _comparer = comparer;
        _hash = hash;
    }

    public bool Equals( T x, T y )
    {
        return _comparer( x, y );
    }

    public int GetHashCode( T obj )
    {
        return _hash( obj );
    }
}

5
เท่าที่ฉันกังวลนี่คือคำตอบที่ถูกต้อง สิ่งใด ๆIEqualityComparer<T>ที่GetHashCodeหลุดออกมาก็เป็นเพียงการหักตรง
ด่านเต๋า

1
@ โจชัวแฟรงค์: ไม่สามารถใช้ความเท่าเทียมกันของแฮชเพื่อบ่งบอกถึงความเท่าเทียมกันได้ - มีเพียงการผกผันเท่านั้นที่เป็นจริง ในระยะสั้น @ Dan Tao ถูกต้องอย่างสมบูรณ์ในสิ่งที่เขาพูดและคำตอบนี้เป็นเพียงการประยุกต์ใช้ข้อเท็จจริงนี้กับคำตอบที่ไม่สมบูรณ์ก่อนหน้านี้
Ruben Bartelink

2
@Ruben Bartelink: ขอบคุณสำหรับการชี้แจง แต่ผมก็ยังไม่เข้าใจนโยบายคร่ำเครียดของคุณ t => 0 ถ้าวัตถุทั้งหมดเสมอกัญชาถึงสิ่งเดียวกัน (ศูนย์) แล้วไม่ได้ว่าแม้จะมากขึ้นเสียกว่าการใช้ obj.GetHashCode ต่อจุด @Dan เต่า? ทำไมไม่บังคับให้ผู้โทรส่งฟังก์ชันแฮชที่ดีอยู่เสมอ
Joshua Frank

1
ดังนั้นจึงไม่สมเหตุสมผลที่จะสันนิษฐานว่าอัลกอริทึมที่กำหนดเองใน Func ที่ให้มานั้นไม่สามารถคืนค่าจริงได้แม้ว่ารหัสแฮชจะแตกต่างกันก็ตาม จุดของคุณที่คืนศูนย์ตลอดเวลาไม่ใช่แค่การแฮชเท่านั้นที่เป็นจริง นั่นเป็นเหตุผลว่าทำไมจึงมีการแฮช Func มากเกินไปเมื่อผู้สร้างโปรไฟล์บอกเราว่าการค้นหาไม่มีประสิทธิภาพเพียงพอ ประเด็นเดียวในทั้งหมดนี้คือหากคุณจะมีอัลกอริทึมการแฮชเริ่มต้นควรเป็นอัลกอริทึมที่ทำงานได้ 100% ตลอดเวลาและไม่มีพฤติกรรมที่ถูกต้องอย่างผิวเผินที่เป็นอันตราย แล้วเราก็สามารถทำงานได้!
Ruben Bartelink

4
กล่าวอีกนัยหนึ่งเนื่องจากคุณใช้ตัวเปรียบเทียบแบบกำหนดเองจึงไม่มีส่วนเกี่ยวข้องกับรหัสแฮชเริ่มต้นของวัตถุที่เกี่ยวข้องกับตัวเปรียบเทียบเริ่มต้นดังนั้นคุณจึงไม่สามารถใช้งานได้
Peet Brits

170

เกี่ยวกับความสำคัญของ GetHashCode

คนอื่น ๆ ได้แสดงความคิดเห็นเกี่ยวกับความจริงที่ว่าการIEqualityComparer<T>ใช้งานแบบกำหนดเองควรมีGetHashCodeวิธีการด้วย แต่ไม่มีใครสนใจที่จะอธิบายว่าทำไมโดยละเอียด

นี่คือเหตุผล คำถามของคุณกล่าวถึงวิธีการขยาย LINQ โดยเฉพาะ สิ่งเหล่านี้เกือบทั้งหมดใช้รหัสแฮชเพื่อให้ทำงานได้อย่างถูกต้องเนื่องจากใช้ตารางแฮชภายในเพื่อประสิทธิภาพ

ลองDistinctยกตัวอย่างเช่น พิจารณาความหมายของวิธีการขยายนี้หากใช้ทั้งหมดเป็นEqualsวิธีการ คุณจะทราบได้อย่างไรว่ามีการสแกนรายการตามลำดับแล้วEqualsหรือไม่หากคุณมีเพียง คุณระบุคอลเล็กชันของค่าทั้งหมดที่คุณได้ดูและตรวจสอบการจับคู่ สิ่งนี้จะส่งผลให้Distinctใช้อัลกอริทึมO (N 2 ) กรณีเลวร้ายที่สุดแทนที่จะเป็น O (N) หนึ่ง!

โชคดีที่ไม่เป็นเช่นนั้น Distinctไม่เพียงแค่ใช้Equals; ก็ใช้GetHashCodeเช่นกัน ในความเป็นจริงมันอย่างไม่ทำงานอย่างถูกต้องโดยไม่ต้องมีIEqualityComparer<T>GetHashCodeที่ให้เหมาะสม ด้านล่างนี้เป็นตัวอย่างที่สร้างขึ้นเพื่อแสดงสิ่งนี้

สมมติว่าฉันมีประเภทต่อไปนี้:

class Value
{
    public string Name { get; private set; }
    public int Number { get; private set; }

    public Value(string name, int number)
    {
        Name = name;
        Number = number;
    }

    public override string ToString()
    {
        return string.Format("{0}: {1}", Name, Number);
    }
}

ตอนนี้บอกว่าฉันมีList<Value>และฉันต้องการค้นหาองค์ประกอบทั้งหมดที่มีชื่อเฉพาะ นี่เป็นกรณีการใช้งานที่สมบูรณ์แบบสำหรับการDistinctใช้ตัวเปรียบเทียบความเท่าเทียมที่กำหนดเอง ลองใช้Comparer<T>คลาสจากคำตอบของ Aku :

var comparer = new Comparer<Value>((x, y) => x.Name == y.Name);

ทีนี้ถ้าเรามีValueองค์ประกอบมากมายที่มีNameคุณสมบัติเดียวกันองค์ประกอบทั้งหมดก็ควรจะยุบรวมเป็นค่าเดียวที่ส่งกลับมาDistinctใช่ไหม มาดูกัน...

var values = new List<Value>();

var random = new Random();
for (int i = 0; i < 10; ++i)
{
    values.Add("x", random.Next());
}

var distinct = values.Distinct(comparer);

foreach (Value x in distinct)
{
    Console.WriteLine(x);
}

เอาท์พุท:

x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377

อืมไม่ได้ผลใช่ไหม

เกี่ยวกับอะไรGroupBy? ลองดูสิ:

var grouped = values.GroupBy(x => x, comparer);

foreach (IGrouping<Value> g in grouped)
{
    Console.WriteLine("[KEY: '{0}']", g);
    foreach (Value x in g)
    {
        Console.WriteLine(x);
    }
}

เอาท์พุท:

[คีย์ = 'x: 1346013431']
x: 1346013431
[คีย์ = 'x: 1388845717']
x: 1388845717
[คีย์ = 'x: 1576754134']
x: 1576754134
[คีย์ = 'x: 1104067189']
x: 1104067189
[คีย์ = 'x: 1144789201']
x: 1144789201
[คีย์ = 'x: 1862076501']
x: 1862076501
[คีย์ = 'x: 1573781440']
x: 1573781440
[คีย์ = 'x: 646797592']
x: 646797592
[คีย์ = 'x: 655632802']
x: 655632802
[คีย์ = 'x: 1206819377']
x: 1206819377

อีกครั้ง: ไม่ได้ผล

หากคุณคิดเกี่ยวกับเรื่องนี้Distinctการใช้HashSet<T>(หรือเทียบเท่า) เป็นการภายในและGroupByการใช้งานDictionary<TKey, List<T>>ภายใน สิ่งนี้สามารถอธิบายได้ว่าทำไมวิธีการเหล่านี้ไม่ได้ผล? ลองสิ่งนี้:

var uniqueValues = new HashSet<Value>(values, comparer);

foreach (Value x in uniqueValues)
{
    Console.WriteLine(x);
}

เอาท์พุท:

x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377

อือ ... เริ่มเข้าท่า?

หวังว่าจากตัวอย่างเหล่านี้จะเห็นได้ชัดเจนว่าเหตุใดการรวมความเหมาะสมGetHashCodeในIEqualityComparer<T>การนำไปใช้งานจึงมีความสำคัญมาก


คำตอบเดิม

ขยายความเกี่ยวกับคำตอบของ orip :

มีการปรับปรุงสองสามอย่างที่สามารถทำได้ที่นี่

  1. ก่อนอื่นฉันจะใช้Func<T, TKey>แทนFunc<T, object>; สิ่งนี้จะป้องกันการชกมวยของคีย์ประเภทค่าในkeyExtractorตัวมันเอง
  2. อย่างที่สองฉันจะเพิ่มwhere TKey : IEquatable<TKey>ข้อ จำกัดจริงๆ สิ่งนี้จะป้องกันการชกมวยในการEqualsโทร ( object.Equalsรับobjectพารามิเตอร์คุณต้องมีIEquatable<TKey>การใช้งานเพื่อรับTKeyพารามิเตอร์โดยไม่ต้องต่อยมวย) เห็นได้ชัดว่าสิ่งนี้อาจก่อให้เกิดข้อ จำกัด ที่รุนแรงเกินไปดังนั้นคุณสามารถสร้างคลาสพื้นฐานโดยไม่มีข้อ จำกัด และคลาสที่ได้รับมาด้วย

นี่คือลักษณะของรหัสผลลัพธ์:

public class KeyEqualityComparer<T, TKey> : IEqualityComparer<T>
{
    protected readonly Func<T, TKey> keyExtractor;

    public KeyEqualityComparer(Func<T, TKey> keyExtractor)
    {
        this.keyExtractor = keyExtractor;
    }

    public virtual bool Equals(T x, T y)
    {
        return this.keyExtractor(x).Equals(this.keyExtractor(y));
    }

    public int GetHashCode(T obj)
    {
        return this.keyExtractor(obj).GetHashCode();
    }
}

public class StrictKeyEqualityComparer<T, TKey> : KeyEqualityComparer<T, TKey>
    where TKey : IEquatable<TKey>
{
    public StrictKeyEqualityComparer(Func<T, TKey> keyExtractor)
        : base(keyExtractor)
    { }

    public override bool Equals(T x, T y)
    {
        // This will use the overload that accepts a TKey parameter
        // instead of an object parameter.
        return this.keyExtractor(x).Equals(this.keyExtractor(y));
    }
}

1
ของคุณวิธีการที่ดูเหมือนจะเป็นเช่นเดียวกับStrictKeyEqualityComparer.Equals KeyEqualityComparer.Equalsไม่TKey : IEquatable<TKey>จำกัด ทำให้TKey.Equalsการทำงานแตกต่างกัน?
Justin Morgan

2
@JustinMorgan: ใช่ - ในกรณีแรกเนื่องจากTKeyอาจเป็นประเภทใดก็ได้โดยพลการคอมไพเลอร์จะใช้วิธีการเสมือนจริงObject.Equalsซึ่งจะต้องใช้พารามิเตอร์ประเภทค่าเช่นint. ในกรณีหลัง แต่เนื่องจากTKeyเป็นข้อ จำกัด ในการดำเนินIEquatable<TKey>การTKey.Equalsวิธีการที่จะนำมาใช้ซึ่งจะไม่จำเป็นต้องมีมวยใด ๆ
ด่านเต๋า

2
น่าสนใจมากขอบคุณสำหรับข้อมูล ฉันไม่รู้เลยว่า GetHashCode มีผลกระทบ LINQ เหล่านี้จนกว่าจะเห็นคำตอบเหล่านี้ น่ารู้สำหรับใช้ในอนาคต
Justin Morgan

1
@JohannesH: น่าจะ! จะได้ขจัดความจำเป็นไปStringKeyEqualityComparer<T, TKey>ด้วย
ด่านเต๋า

1
+1 @DanTao: ขอบคุณมากสำหรับการอธิบายที่ยอดเยี่ยมว่าทำไมเราไม่ควรละเลยรหัสแฮชเมื่อกำหนดความเท่าเทียมกันใน. Net
Marcelo Cantos

118

เมื่อคุณต้องการปรับแต่งการตรวจสอบความเท่าเทียมกัน 99% ของเวลาที่คุณสนใจที่จะกำหนดคีย์เพื่อเปรียบเทียบไม่ใช่การเปรียบเทียบเอง

นี่อาจเป็นวิธีแก้ปัญหาที่สวยงาม (แนวคิดจากวิธีการจัดเรียงรายการของ Python )

การใช้งาน:

var foo = new List<string> { "abc", "de", "DE" };

// case-insensitive distinct
var distinct = foo.Distinct(new KeyEqualityComparer<string>( x => x.ToLower() ) );

KeyEqualityComparerระดับ:

public class KeyEqualityComparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, object> keyExtractor;

    public KeyEqualityComparer(Func<T,object> keyExtractor)
    {
        this.keyExtractor = keyExtractor;
    }

    public bool Equals(T x, T y)
    {
        return this.keyExtractor(x).Equals(this.keyExtractor(y));
    }

    public int GetHashCode(T obj)
    {
        return this.keyExtractor(obj).GetHashCode();
    }
}

3
นี้เป็นมากดีกว่าคำตอบของ aku
SLaks

แนวทางที่ถูกต้องแน่นอน มีการปรับปรุงสองสามอย่างที่สามารถทำได้ในความคิดของฉันซึ่งฉันได้พูดถึงในคำตอบของฉันเอง
ด่านเต๋า

1
นี่เป็นรหัสที่สวยงามมาก แต่ไม่ตอบคำถามซึ่งเป็นสาเหตุที่ฉันยอมรับคำตอบของ @ aku แทน ฉันต้องการกระดาษห่อหุ้มสำหรับ Func <T, T, bool> และฉันไม่มีข้อกำหนดในการแยกคีย์เนื่องจากคีย์ถูกแยกออกจากพจนานุกรมแล้ว
Marcelo Cantos

6
@Marcelo: ไม่เป็นไรคุณทำได้ แต่โปรดทราบว่าหากคุณจะใช้แนวทางของ @ aku คุณควรเพิ่มFunc<T, int>รหัสเพื่อระบุTค่าแฮช(ตามที่แนะนำไว้เช่นคำตอบของ Ruben ) มิฉะนั้นการIEqualityComparer<T>ใช้งานที่คุณทิ้งไว้จะค่อนข้างเสียหายโดยเฉพาะอย่างยิ่งในเรื่องประโยชน์ของมันในวิธีการขยาย LINQ ดูคำตอบของฉันสำหรับการอภิปรายว่าเหตุใดจึงเป็นเช่นนั้น
ด่านเต๋า

นี่เป็นสิ่งที่ดี แต่ถ้าคีย์ที่เลือกเป็นประเภทค่าจะมีการชกมวยที่ไม่จำเป็น บางทีอาจจะดีกว่าถ้ามี TKey สำหรับกำหนดคีย์
Graham Ambrose

48

ฉันเกรงว่าจะไม่มีกระดาษห่อหุ้มแบบนั้นออกมาจากกล่อง อย่างไรก็ตามการสร้างมันไม่ยาก:

class Comparer<T>: IEqualityComparer<T>
{
    private readonly Func<T, T, bool> _comparer;

    public Comparer(Func<T, T, bool> comparer)
    {
        if (comparer == null)
            throw new ArgumentNullException("comparer");

        _comparer = comparer;
    }

    public bool Equals(T x, T y)
    {
        return _comparer(x, y);
    }

    public int GetHashCode(T obj)
    {
        return obj.ToString().ToLower().GetHashCode();
    }
}

...

Func<int, int, bool> f = (x, y) => x == y;
var comparer = new Comparer<int>(f);
Console.WriteLine(comparer.Equals(1, 1));
Console.WriteLine(comparer.Equals(1, 2));

1
อย่างไรก็ตามโปรดใช้ความระมัดระวังในการใช้ GetHashCode นั้น หากคุณกำลังจะใช้มันในตารางแฮชบางประเภทคุณจะต้องการบางสิ่งที่แข็งแกร่งกว่านี้
thecoop

46
รหัสนี้มีปัญหาร้ายแรง! เป็นเรื่องง่ายที่จะสร้างคลาสที่มีวัตถุสองชิ้นที่มีค่าเท่ากันในแง่ของตัวเปรียบเทียบนี้ แต่มีรหัสแฮชต่างกัน
empi

10
เพื่อแก้ไขปัญหานี้คลาสต้องการสมาชิกอื่นprivate readonly Func<T, int> _hashCodeResolverที่ต้องส่งผ่านในตัวสร้างและใช้ในGetHashCode(...)เมธอด
herzmeister

6
ฉันอยากรู้: ทำไมคุณถึงใช้obj.ToString().ToLower().GetHashCode()แทนobj.GetHashCode()?
Justin Morgan

3
สถานที่ในเฟรมเวิร์กที่ใช้การIEqualityComparer<T>แฮชเบื้องหลังอย่างสม่ำเสมอ (เช่น GroupBy ของ LINQ, Distinct, except, Join ฯลฯ ) และสัญญา MS เกี่ยวกับการแฮชในการใช้งานนี้ไม่สมบูรณ์ นี่คือเอกสารที่ตัดตอนมาของ MS: "การนำไปใช้งานจำเป็นเพื่อให้แน่ใจว่าหากเมธอด Equals คืนค่าจริงสำหรับสองอ็อบเจ็กต์ x และ y ค่าที่ส่งคืนโดยเมธอด GetHashCode สำหรับ x จะต้องเท่ากับค่าที่ส่งคืนสำหรับ y" ดู: msdn.microsoft.com/en-us/library/ms132155
devgeezer

22

เหมือนกับคำตอบของ Dan Tao แต่มีการปรับปรุงเล็กน้อย:

  1. ขึ้นอยู่กับการEqualityComparer<>.Defaultที่จะทำจริงเปรียบเทียบเพื่อที่จะหลีกเลี่ยงมวยประเภทค่า ( structs) IEquatable<>ที่ได้ดำเนินการ

  2. ตั้งแต่ใช้มันไม่ระเบิดEqualityComparer<>.Defaultnull.Equals(something)

  3. มีกระดาษห่อแบบคงIEqualityComparer<>ที่ซึ่งจะมีวิธีการแบบคงที่ในการสร้างอินสแตนซ์ของตัวเปรียบเทียบ - ช่วยลดการโทร เปรียบเทียบ

    Equality<Person>.CreateComparer(p => p.ID);

    กับ

    new EqualityComparer<Person, int>(p => p.ID);
  4. เพิ่มการโอเวอร์โหลดเพื่อระบุIEqualityComparer<>สำหรับคีย์

ห้องเรียน:

public static class Equality<T>
{
    public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector)
    {
        return CreateComparer(keySelector, null);
    }

    public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector, 
                                                         IEqualityComparer<V> comparer)
    {
        return new KeyEqualityComparer<V>(keySelector, comparer);
    }

    class KeyEqualityComparer<V> : IEqualityComparer<T>
    {
        readonly Func<T, V> keySelector;
        readonly IEqualityComparer<V> comparer;

        public KeyEqualityComparer(Func<T, V> keySelector, 
                                   IEqualityComparer<V> comparer)
        {
            if (keySelector == null)
                throw new ArgumentNullException("keySelector");

            this.keySelector = keySelector;
            this.comparer = comparer ?? EqualityComparer<V>.Default;
        }

        public bool Equals(T x, T y)
        {
            return comparer.Equals(keySelector(x), keySelector(y));
        }

        public int GetHashCode(T obj)
        {
            return comparer.GetHashCode(keySelector(obj));
        }
    }
}

คุณอาจใช้สิ่งนี้:

var comparer1 = Equality<Person>.CreateComparer(p => p.ID);
var comparer2 = Equality<Person>.CreateComparer(p => p.Name);
var comparer3 = Equality<Person>.CreateComparer(p => p.Birthday.Year);
var comparer4 = Equality<Person>.CreateComparer(p => p.Name, StringComparer.CurrentCultureIgnoreCase);

บุคคลเป็นคลาสง่ายๆ:

class Person
{
    public int ID { get; set; }
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
}

3
+1 สำหรับการนำเสนอการใช้งานที่ช่วยให้คุณสามารถระบุตัวเปรียบเทียบสำหรับคีย์ได้ นอกจากนี้ยังให้ความยืดหยุ่นมากขึ้นแล้วยังหลีกเลี่ยงประเภทมูลค่าการชกมวยสำหรับทั้งการเปรียบเทียบและการแฮช
devgeezer

2
นี่คือคำตอบที่ละเอียดที่สุดที่นี่ ฉันเพิ่มการตรวจสอบค่าว่างด้วย สมบูรณ์
nawfal

11
public class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    readonly Func<T, T, bool> _comparer;
    readonly Func<T, int> _hash;

    public FuncEqualityComparer( Func<T, T, bool> comparer )
        : this( comparer, t => t.GetHashCode())
    {
    }

    public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
    {
        _comparer = comparer;
        _hash = hash;
    }

    public bool Equals( T x, T y )
    {
        return _comparer( x, y );
    }

    public int GetHashCode( T obj )
    {
        return _hash( obj );
    }
}

พร้อมนามสกุล: -

public static class SequenceExtensions
{
    public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer )
    {
        return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer ) );
    }

    public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer, Func<T, int> hash )
    {
        return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer, hash ) );
    }
}

@Sam (ซึ่งไม่มีอยู่แล้วในความคิดเห็นนี้): ทำความสะอาดโค้ดโดยไม่ปรับพฤติกรรม (และ +1) เพิ่ม Riff ที่stackoverflow.com/questions/98033/…
Ruben Bartelink

6

คำตอบของ orip ดีมาก

ต่อไปนี้เป็นวิธีการขยายเล็กน้อยเพื่อให้ง่ายยิ่งขึ้น:

public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, object>    keyExtractor)
{
    return list.Distinct(new KeyEqualityComparer<T>(keyExtractor));
}
var distinct = foo.Distinct(x => x.ToLower())

2

ฉันจะตอบคำถามของตัวเอง ในการปฏิบัติกับ Dictionaries เป็นชุดวิธีที่ง่ายที่สุดคือการใช้การดำเนินการ set กับ dict.Keys จากนั้นแปลงกลับเป็น Dictionaries ด้วย Enumerable ToDictionary (... )


2

การใช้งานที่ (ข้อความภาษาเยอรมัน) การใช้ IEquality เปรียบเทียบกับนิพจน์แลมบ์ดา สำคัญกับค่าว่างและใช้วิธีการขยายเพื่อสร้าง IEqualityComparer

ในการสร้าง IEqualityComparer ในสหภาพ Linq คุณต้องเขียน

persons1.Union(persons2, person => person.LastName)

ผู้เปรียบเทียบ:

public class LambdaEqualityComparer<TSource, TComparable> : IEqualityComparer<TSource>
{
  Func<TSource, TComparable> _keyGetter;

  public LambdaEqualityComparer(Func<TSource, TComparable> keyGetter)
  {
    _keyGetter = keyGetter;
  }

  public bool Equals(TSource x, TSource y)
  {
    if (x == null || y == null) return (x == null && y == null);
    return object.Equals(_keyGetter(x), _keyGetter(y));
  }

  public int GetHashCode(TSource obj)
  {
    if (obj == null) return int.MinValue;
    var k = _keyGetter(obj);
    if (k == null) return int.MaxValue;
    return k.GetHashCode();
  }
}

คุณต้องเพิ่มวิธีการขยายเพื่อรองรับการอนุมานประเภท

public static class LambdaEqualityComparer
{
       // source1.Union(source2, lambda)
        public static IEnumerable<TSource> Union<TSource, TComparable>(
           this IEnumerable<TSource> source1, 
           IEnumerable<TSource> source2, 
            Func<TSource, TComparable> keySelector)
        {
            return source1.Union(source2, 
               new LambdaEqualityComparer<TSource, TComparable>(keySelector));
       }
   }

1

การเพิ่มประสิทธิภาพเพียงครั้งเดียว: เราสามารถใช้ EqualityComparer ที่พร้อมใช้งานสำหรับการเปรียบเทียบมูลค่าแทนที่จะมอบหมายให้

สิ่งนี้จะทำให้การใช้งานสะอาดขึ้นเนื่องจากตรรกะการเปรียบเทียบจริงตอนนี้ยังคงอยู่ใน GetHashCode () และ Equals () ซึ่งคุณอาจมีงานมากเกินไปแล้ว

นี่คือรหัส:

public class MyComparer<T> : IEqualityComparer<T> 
{ 
  public bool Equals(T x, T y) 
  { 
    return EqualityComparer<T>.Default.Equals(x, y); 
  } 

  public int GetHashCode(T obj) 
  { 
    return obj.GetHashCode(); 
  } 
} 

อย่าลืมโหลดเมธอด GetHashCode () และ Equals () มากเกินไปบนวัตถุของคุณ

โพสต์นี้ช่วยฉัน: c # เปรียบเทียบค่าทั่วไปสองค่า

Sushil


1
NB ปัญหาเดียวกับที่ระบุไว้ในความคิดเห็นบนstackoverflow.com/questions/98033/… - CANT ถือว่า obj.GetHashCode () เหมาะสม
Ruben Bartelink

4
ฉันไม่เข้าใจจุดประสงค์ของสิ่งนี้ คุณได้สร้างตัวเปรียบเทียบความเท่าเทียมที่เทียบเท่ากับตัวเปรียบเทียบความเท่าเทียมเริ่มต้น แล้วทำไมคุณไม่ใช้มันโดยตรง?
CodesInChaos

1

คำตอบของ oripดีมาก ขยายความเกี่ยวกับคำตอบของ orip:

ฉันคิดว่าคีย์ของโซลูชันคือใช้ "วิธีการขยาย" เพื่อโอน "ประเภทที่ไม่ระบุตัวตน"

    public static class Comparer 
    {
      public static IEqualityComparer<T> CreateComparerForElements<T>(this IEnumerable<T> enumerable, Func<T, object> keyExtractor)
      {
        return new KeyEqualityComparer<T>(keyExtractor);
      }
    }

การใช้งาน:

var n = ItemList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList();
n.AddRange(OtherList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList(););
n = n.Distinct(x=>new{Vchr=x.Vchr,Id=x.Id}).ToList();

0
public static Dictionary<TKey, TValue> Distinct<TKey, TValue>(this IEnumerable<TValue> items, Func<TValue, TKey> selector)
  {
     Dictionary<TKey, TValue> result = null;
     ICollection collection = items as ICollection;
     if (collection != null)
        result = new Dictionary<TKey, TValue>(collection.Count);
     else
        result = new Dictionary<TKey, TValue>();
     foreach (TValue item in items)
        result[selector(item)] = item;
     return result;
  }

ทำให้สามารถเลือกคุณสมบัติที่มีแลมด้าดังนี้: .Select(y => y.Article).Distinct(x => x.ArticleID);


-2

ฉันไม่รู้จักคลาสที่มีอยู่ แต่มีบางอย่างเช่น:

public class MyComparer<T> : IEqualityComparer<T>
{
  private Func<T, T, bool> _compare;
  MyComparer(Func<T, T, bool> compare)
  {
    _compare = compare;
  }

  public bool Equals(T x, Ty)
  {
    return _compare(x, y);
  }

  public int GetHashCode(T obj)
  {
    return obj.GetHashCode();
  }
}

หมายเหตุ: ฉันยังไม่ได้รวบรวมและเรียกใช้สิ่งนี้จริง ๆ ดังนั้นอาจมีการพิมพ์ผิดหรือข้อบกพร่องอื่น ๆ


1
NB ปัญหาเดียวกับที่ระบุไว้ในความคิดเห็นบนstackoverflow.com/questions/98033/… - CANT ถือว่า obj.GetHashCode () เหมาะสม
Ruben Bartelink
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.