LINQ เลือก Distinct ที่มีประเภท Anonymous


150

ดังนั้นฉันมีชุดของวัตถุ ประเภทที่แน่นอนไม่สำคัญ จากนั้นฉันต้องการแยกคู่ที่ไม่ซ้ำกันของคู่ของคุณสมบัติเฉพาะดังนั้น:

myObjectCollection.Select(item=>new
                                {
                                     Alpha = item.propOne,
                                     Bravo = item.propTwo
                                }
                 ).Distinct();

ดังนั้นคำถามของฉันคือ: ความแตกต่างในกรณีนี้จะใช้วัตถุเริ่มต้นเท่ากับ (ซึ่งจะไร้ประโยชน์กับฉันเนื่องจากวัตถุแต่ละชิ้นเป็นของใหม่) หรือสามารถบอกให้ทำสิ่งต่าง ๆ ได้ (ในกรณีนี้ค่าที่เท่ากันของ Alpha และ Bravo => อินสแตนซ์ที่เท่ากัน)? มีวิธีใดบ้างที่จะบรรลุผลดังกล่าวหากไม่ได้ผล


LINQ-to-Objects หรือ LINQ-to-SQL นี้หรือไม่ ถ้าแค่วัตถุคุณอาจโชคไม่ดี อย่างไรก็ตามถ้า L2S มันอาจทำงานได้เนื่องจาก DISTINCT จะถูกส่งไปยังคำสั่ง SQL
เจมส์เคอร์แร

คำตอบ:


188

อ่านโพสต์ที่ยอดเยี่ยมของ K. Scott Allen ได้ที่นี่:

และความเท่าเทียมกันสำหรับทุกคน ... ประเภทที่ไม่เปิดเผยตัว

คำตอบสั้น ๆ (และฉันพูด):

เปลี่ยนคอมไพเลอร์ C # แทนที่ Equals และ GetHashCode สำหรับประเภทที่ไม่ระบุตัวตน การใช้งานวิธีการแทนที่ทั้งสองวิธีใช้คุณสมบัติสาธารณะทั้งหมดบนชนิดเพื่อคำนวณรหัสแฮชของวัตถุและทดสอบความเท่าเทียมกัน หากวัตถุสองชนิดที่ไม่ระบุชื่อเดียวกันมีค่าเหมือนกันทั้งหมดสำหรับคุณสมบัติของพวกเขา - วัตถุนั้นเท่ากัน

ดังนั้นจึงปลอดภัยอย่างยิ่งที่จะใช้วิธี Distinct () ในการสืบค้นที่ส่งคืนชนิดที่ไม่ระบุตัวตน


2
ฉันคิดว่านี่เป็นเรื่องจริงเท่านั้นหากคุณสมบัตินั้นเป็นประเภทค่าหรือใช้ความเท่าเทียมกันของมูลค่า - ดูคำตอบของฉัน
tvanfosson

ใช่เพราะมันใช้ GetHashCode กับแต่ละคุณสมบัติมันจะทำงานได้ก็ต่อเมื่อแต่ละคุณสมบัติมีการใช้งานที่เป็นเอกลักษณ์ของตัวเอง ฉันคิดว่ากรณีการใช้งานส่วนใหญ่จะเกี่ยวข้องกับประเภทที่เรียบง่ายเป็นคุณสมบัติเท่านั้นดังนั้นจึงปลอดภัยโดยทั่วไป
Matt Hamilton

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

3
มันอาจคุ้มค่าที่จะร้องเรียน MS เพื่อแนะนำไวยากรณ์ "คีย์" ใน C # ที่ VB มี (ที่คุณสามารถระบุคุณสมบัติบางอย่างของประเภทที่ไม่ระบุตัวตนเป็น 'คีย์หลัก' - ดูโพสต์บล็อกที่ฉันเชื่อมโยง)
Matt Hamilton

1
บทความที่น่าสนใจมาก ขอบคุณ!
Alexander Prokofyev

14
public class DelegateComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, bool> _equals;
    private Func<T, int> _hashCode;
    public DelegateComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        _equals= equals;
        _hashCode = hashCode;
    }
    public bool Equals(T x, T y)
    {
        return _equals(x, y);
    }

    public int GetHashCode(T obj)
    {
        if(_hashCode!=null)
            return _hashCode(obj);
        return obj.GetHashCode();
    }       
}

public static class Extensions
{
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, 
        Func<T, T, bool> equals, Func<T,int> hashCode)
    {
        return items.Distinct(new DelegateComparer<T>(equals, hashCode));    
    }
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items,
        Func<T, T, bool> equals)
    {
        return items.Distinct(new DelegateComparer<T>(equals,null));
    }
}

var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
            .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();

ขออภัยสำหรับการจัดรูปแบบสับสนก่อนหน้านี้


ส่วนขยายนี้ไม่สามารถจัดการกับชนิดและobject objectหากทั้งสองobjectเป็นstringก็ยังคงกลับแถวที่ซ้ำกัน ลองFirstNameเป็น typeof objectและกำหนดด้วยเหมือนกันstringมี
CallMeLaNN

นี่เป็นคำตอบที่ดีสำหรับวัตถุที่พิมพ์ แต่ไม่จำเป็นสำหรับประเภทที่ไม่ระบุชื่อ
crokusek

5

น่าสนใจว่ามันใช้งานได้ใน C # แต่ไม่ใช่ใน VB

ส่งคืน 26 ตัวอักษร:

var MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ";
MyBet.ToCharArray()
.Select(x => new {lower = x.ToString().ToLower(), upper = x.ToString().ToUpper()})
.Distinct()
.Dump();

ส่งคืน 52 ...

Dim MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"
MyBet.ToCharArray() _
.Select(Function(x) New With {.lower = x.ToString.ToLower(), .upper = x.ToString.ToUpper()}) _
.Distinct() _
.Dump()

11
หากคุณเพิ่มKeyคำหลักลงในประเภทที่ไม่ระบุตัวตน.Distinct()จะทำงานได้ตามที่ต้องการ (เช่นNew With { Key .lower = x.ToString.ToLower(), Key .upper = x.ToString.ToUpper()})
Cᴏʀʏ

3
คอรี่นั้นถูกต้อง คำแปลที่ถูกต้องของรหัส C # คือnew {A = b} New {Key .A = b}คุณสมบัติที่ไม่ใช่คีย์ในคลาส VB ที่ไม่ระบุชื่อนั้นไม่แน่นอนซึ่งเป็นเหตุผลที่พวกเขาเปรียบเทียบโดยอ้างอิง ใน C # คุณสมบัติทั้งหมดของคลาสที่ไม่ระบุชื่อจะไม่เปลี่ยนรูป
Heinzi

4

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


2

คุณสามารถสร้างวิธีการขยายความแตกต่างของคุณเองซึ่งแสดงออกแลมบ์ดา นี่คือตัวอย่าง

สร้างคลาสที่มาจากอินเทอร์เฟซ IEqualityComparer

public class DelegateComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, bool> _equals;
    private Func<T, int> _hashCode;
    public DelegateComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        _equals= equals;
        _hashCode = hashCode;
    }
    public bool Equals(T x, T y)
    {
        return _equals(x, y);
    }

    public int GetHashCode(T obj)
    {
        if(_hashCode!=null)
            return _hashCode(obj);
        return obj.GetHashCode();
    }       
}

จากนั้นสร้างวิธีการขยายแบบพิเศษของคุณ

public static class Extensions
{
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, 
        Func<T, T, bool> equals, Func<T,int> hashCode)
    {
        return items.Distinct(new DelegateComparer<T>(equals, hashCode));    
    }
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items,
        Func<T, T, bool> equals)
    {
        return items.Distinct(new DelegateComparer<T>(equals,null));
    }
}

และคุณสามารถใช้วิธีนี้เพื่อค้นหารายการที่แตกต่าง

var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
            .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();

ส่วนขยายนี้ไม่สามารถจัดการกับชนิดและobject objectหากทั้งสองobjectเป็นstringก็ยังคงกลับแถวที่ซ้ำกัน ลองFirstNameเป็น typeof objectและกำหนดด้วยเหมือนกันstringมี
CallMeLaNN

0

ถ้าAlphaและทั้งสืบทอดจากระดับธรรมดาที่คุณจะสามารถที่จะสั่งการตรวจสอบความเท่าเทียมกันในระดับผู้ปกครองโดยการใช้BravoIEquatable<T>

ตัวอย่างเช่น:

public class CommonClass : IEquatable<CommonClass>
{
    // needed for Distinct()
    public override int GetHashCode() 
    {
        return base.GetHashCode();
    }

    public bool Equals(CommonClass other)
    {
        if (other == null) return false;
        return [equality test];
    }
}

ดังนั้นถ้าคุณใช้เป็นคุณสมบัติของคลาสชนิดไม่ระบุชื่อของคุณที่ใช้ IEquatable <T> Equals จะถูกเรียกใช้แทนพฤติกรรมเริ่มต้น (ตรวจสอบคุณสมบัติสาธารณะทั้งหมดผ่านการสะท้อนภาพ)
D_Guidi

0

เฮ้ฉันมีปัญหาเดียวกันและฉันพบวิธีแก้ปัญหา คุณต้องใช้อินเทอร์เฟซ IEquatable หรือเพียงแค่แทนที่ (Equals & GetHashCode) วิธีการ แต่นี่ไม่ใช่กลอุบายเคล็ดลับที่มาในวิธี GetHashCode คุณไม่ควรส่งคืนรหัสแฮชของวัตถุในคลาสของคุณ แต่คุณควรส่งคืนค่าแฮชของคุณสมบัติที่คุณต้องการเปรียบเทียบเช่นนั้น

public override bool Equals(object obj)
    {
        Person p = obj as Person;
        if ( obj == null )
            return false;
        if ( object.ReferenceEquals( p , this ) )
            return true;
        if ( p.Age == this.Age && p.Name == this.Name && p.IsEgyptian == this.IsEgyptian )
            return true;
        return false;
        //return base.Equals( obj );
    }
    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }

อย่างที่คุณเห็นฉันได้คลาสเรียกว่า person มีคุณสมบัติ 3 อย่าง (ชื่อ, อายุ, IsEgyptian "เพราะฉันเป็น") ใน GetHashCode ฉันส่งคืนแฮชของคุณสมบัติ Name ไม่ใช่วัตถุบุคคล

ลองใช้และจะใช้งานได้กับ ISA ขอบคุณ Modather Sadik


1
GetHashCode ควรใช้ฟิลด์และคุณสมบัติเดียวกันทั้งหมดที่ใช้ในการเปรียบเทียบเพื่อความเท่าเทียมกันไม่ใช่แค่หนึ่งในนั้น iepublic override int GetHashCode() { return this.Name.GetHashCode() ^ this.Age.GetHashCode() ^ this.IsEgyptian.GetHashCode(); }
JG ใน SD

สำหรับข้อมูลเกี่ยวกับการสร้างอัลกอริทึมแฮชที่ดี: stackoverflow.com/questions/263400/…
JG ใน SD

0

เพื่อให้มันทำงานใน VB.NET คุณจะต้องระบุKeyคำหลักก่อนทุกคุณสมบัติในประเภทที่ไม่ระบุชื่อเช่นนี้:

myObjectCollection.Select(Function(item) New With
{
    Key .Alpha = item.propOne,
    Key .Bravo = item.propTwo
}).Distinct()

ฉันดิ้นรนกับสิ่งนี้ฉันคิดว่า VB.NET ไม่รองรับคุณสมบัติประเภทนี้ แต่จริงๆแล้วมันทำ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.