LINQ Chọn Phân biệt với các loại ẩn danh


150

Vì vậy, tôi có một bộ sưu tập các đối tượng. Loại chính xác không quan trọng. Từ đó tôi muốn trích xuất tất cả các cặp duy nhất của một cặp thuộc tính cụ thể:

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

Vì vậy, câu hỏi của tôi là: Sẽ khác biệt trong trường hợp này sử dụng đối tượng mặc định bằng (sẽ vô dụng với tôi, vì mỗi đối tượng là mới) hoặc có thể được yêu cầu thực hiện một bằng khác nhau (trong trường hợp này, các giá trị bằng nhau của Alpha và Bravo => trường hợp bằng nhau)? Có cách nào để đạt được kết quả đó không, nếu điều này không làm được?


Đây là LINQ-to-Object hay LINQ-to-SQL? Nếu chỉ là đồ vật, có lẽ bạn đã hết may mắn. Tuy nhiên, nếu L2S, thì nó có thể hoạt động, vì DISTINCT sẽ được truyền vào câu lệnh SQL.
James Curran

Câu trả lời:


188

Hãy đọc qua bài viết xuất sắc của K. Scott Allen tại đây:

Và bình đẳng cho tất cả ... Các loại ẩn danh

Câu trả lời ngắn (và tôi trích dẫn):

Hóa ra trình biên dịch C # ghi đè Equals và GetHashCode cho các loại ẩn danh. Việc thực hiện hai phương thức được ghi đè sử dụng tất cả các thuộc tính công khai trên loại để tính mã băm của đối tượng và kiểm tra sự bằng nhau. Nếu hai đối tượng cùng loại ẩn danh có tất cả các giá trị giống nhau cho các thuộc tính của chúng - thì các đối tượng bằng nhau.

Vì vậy, hoàn toàn an toàn khi sử dụng phương thức Phân biệt () trên truy vấn trả về các loại ẩn danh.


2
Điều này chỉ đúng, tôi nghĩ, nếu bản thân các thuộc tính là loại giá trị hoặc thực hiện công bằng giá trị - hãy xem câu trả lời của tôi.
tvanfosson

Có, vì nó sử dụng GetHashCode trên mỗi thuộc tính nên nó sẽ chỉ hoạt động nếu mỗi thuộc tính có cách triển khai duy nhất của riêng nó. Tôi nghĩ rằng hầu hết các trường hợp sử dụng sẽ chỉ liên quan đến các loại đơn giản như các thuộc tính nên thường an toàn.
Matt Hamilton

4
Điều đó có nghĩa là sự bình đẳng của hai trong số các loại ẩn danh phụ thuộc vào sự bình đẳng của các thành viên, điều này cũng ổn đối với tôi, vì các thành viên được xác định ở đâu đó tôi có thể nhận được và ghi đè lên sự bình đẳng nếu tôi phải. Tôi chỉ không muốn tạo một lớp cho việc này chỉ để ghi đè bằng.
GWLlosa

3
Có thể đáng để MS yêu cầu giới thiệu cú pháp "khóa" vào C # mà VB có (nơi bạn có thể chỉ định một số thuộc tính nhất định của một loại ẩn danh là 'khóa chính' - xem bài đăng trên blog tôi liên kết).
Matt Hamilton

1
Bài viết rất thú vị. Cảm ơn!
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();

Xin lỗi vì định dạng sai lầm trước đó


Phần mở rộng này không thể xử lý loại objectobject. Nếu cả hai objectstringnó vẫn trả lại hàng trùng lặp. Hãy thử FirstNamelà typeof objectvà gán với cùng stringở đó.
CallMeLaNN

Đây là một câu trả lời tuyệt vời cho các đối tượng gõ nhưng không cần thiết cho các loại ẩn danh.
crokusek

5

Điều thú vị là nó hoạt động trong C # nhưng không phải trong VB

Trả về 26 chữ cái:

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

Trả về 52 ...

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

11
Nếu bạn thêm Keytừ khóa vào loại ẩn danh, .Distinct()nó sẽ hoạt động như dự định (ví dụ New With { Key .lower = x.ToString.ToLower(), Key .upper = x.ToString.ToUpper()}).
Cᴏʀʏ

3
Cory đã đúng. Bản dịch chính xác của mã C # new {A = b}New {Key .A = b}. Các thuộc tính không khóa trong các lớp VB ẩn danh có thể thay đổi, đó là lý do tại sao chúng được so sánh bằng tham chiếu. Trong C #, tất cả các thuộc tính của các lớp ẩn danh là bất biến.
Heinzi

4

Tôi đã chạy thử nghiệm một chút và thấy rằng nếu các thuộc tính là các loại giá trị, nó có vẻ hoạt động tốt. Nếu chúng không phải là loại giá trị, thì loại nhu cầu cung cấp các triển khai Equals và GetHashCode của riêng nó để nó hoạt động. Chuỗi, tôi sẽ nghĩ, sẽ làm việc.


2

Bạn có thể tạo phương thức Mở rộng riêng biệt có biểu thức lambda. Đây là một ví dụ

Tạo một lớp xuất phát từ giao diện 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();
    }       
}

Sau đó, tạo phương thức Mở rộng riêng biệt của bạn

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

và bạn có thể sử dụng phương pháp này để tìm các mục khác biệt

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

Phần mở rộng này không thể xử lý loại objectobject. Nếu cả hai objectstringnó vẫn trả lại hàng trùng lặp. Hãy thử FirstNamelà typeof objectvà gán với cùng stringở đó.
CallMeLaNN

0

Nếu AlphaBravocả hai đều kế thừa từ một lớp chung, bạn sẽ có thể ra lệnh kiểm tra đẳng thức trong lớp cha bằng cách thực hiệnIEquatable<T> .

Ví dụ:

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];
    }
}

vì vậy, nếu bạn sử dụng làm thuộc tính của các loại loại ẩn danh thực hiện IEquitable <T>, Equals được gọi thay vì hành vi mặc định (kiểm tra tất cả các thuộc tính công cộng thông qua sự phản chiếu?)
D_Guidi

0

Này, tôi có cùng một vấn đề và tôi tìm thấy một giải pháp. Bạn phải triển khai giao diện IEquitable hoặc đơn giản là ghi đè Phương thức (Bằng & GetHashCode). Nhưng đây không phải là mẹo, mẹo đến trong Phương thức GetHashCode. Bạn không nên trả về mã băm của đối tượng của lớp nhưng bạn nên trả về hàm băm của thuộc tính bạn muốn so sánh như thế.

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

Như bạn thấy, tôi có một lớp được gọi là người có 3 thuộc tính (Tên, Tuổi, IsE Ai Cập "Vì tôi là") Trong GetHashCode tôi đã trả về hàm băm của thuộc tính Tên không phải là đối tượng Person.

Hãy thử nó và nó sẽ hoạt động. Cảm ơn bạn, Modather Sadik


1
GetHashCode nên sử dụng tất cả các trường và thuộc tính giống nhau được sử dụng để so sánh cho đẳng thức, không chỉ một trong số chúng. tức làpublic override int GetHashCode() { return this.Name.GetHashCode() ^ this.Age.GetHashCode() ^ this.IsEgyptian.GetHashCode(); }
JG trong SD

Để biết thông tin về cách tạo thuật toán băm tốt: stackoverflow.com/questions/263400/ từ
JG trong SD

0

Để nó hoạt động trong VB.NET, bạn cần chỉ định Keytừ khóa trước mỗi thuộc tính trong loại ẩn danh, giống như sau:

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

Tôi đã vật lộn với điều này, tôi nghĩ VB.NET không hỗ trợ loại tính năng này, nhưng thực tế nó có.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.