Kết thúc một đại biểu trong IEqualityComparer


127

Một số chức năng Linq.Enable có thể có một IEqualityComparer<T>. Có một lớp bao bọc thuận tiện thích nghi delegate(T,T)=>boolđể thực hiện IEqualityComparer<T>không? Thật dễ dàng để viết một cái (nếu bạn bỏ qua vấn đề với việc xác định mã băm chính xác), nhưng tôi muốn biết liệu có giải pháp vượt trội nào không.

Cụ thể, tôi muốn thực hiện cài đặt các thao tác trên Dictionarys, chỉ sử dụng Khóa để xác định tư cách thành viên (trong khi vẫn giữ các giá trị theo các quy tắc khác nhau).

Câu trả lời:


44

Thông thường, tôi sẽ giải quyết vấn đề này bằng cách bình luận @Sam về câu trả lời (Tôi đã thực hiện một số chỉnh sửa trên bài đăng gốc để dọn dẹp nó một chút mà không thay đổi hành vi.)

Sau đây là câu trả lời của tôi về câu trả lời của @ Sam , với bản sửa lỗi quan trọng [IMNSHO] cho chính sách băm mặc định: -

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
Theo tôi nghĩ đây là câu trả lời đúng . Bất kỳ IEqualityComparer<T>cái gì rời GetHashCodekhỏi chỉ là thẳng lên bị hỏng.
Dan Tao

1
@Joshua Frank: Không hợp lệ khi sử dụng đẳng thức băm để ngụ ý sự bình đẳng - chỉ có điều ngược lại là đúng. Nói tóm lại, @Dan Tao hoàn toàn chính xác trong những gì anh ta nói, và câu trả lời này chỉ đơn giản là ứng dụng của thực tế này cho một câu trả lời chưa hoàn chỉnh trước đây
Ruben Bartelink

2
@Ruben Bartelink: Cảm ơn bạn đã làm rõ. Nhưng tôi vẫn không hiểu chính sách băm của bạn là t => 0. Nếu tất cả các đối tượng luôn băm vào cùng một thứ (không), thì điều đó bị phá vỡ nhiều hơn so với sử dụng obj.GetHashCode, theo quan điểm của @Dan Tao không? Tại sao không luôn luôn buộc người gọi cung cấp một hàm băm tốt?
Joshua Frank

1
Do đó, không hợp lý khi giả định rằng một thuật toán tùy ý trong Func mà nó được cung cấp không thể trả về đúng mặc dù mã băm là khác nhau. Quan điểm của bạn rằng trả về số 0 mọi lúc chỉ là không băm là đúng. Đó là lý do tại sao có quá tải băm Func khi trình hồ sơ cho chúng ta tìm kiếm không đủ hiệu quả. Điểm duy nhất trong tất cả những điều này là nếu bạn sắp có một thuật toán băm mặc định, thì nó phải là một thuật toán hoạt động 100% thời gian và không có hành vi đúng đắn bề ngoài nguy hiểm. Và sau đó chúng ta có thể làm việc trên hiệu suất!
Ruben Bartelink

4
Nói cách khác, vì bạn đang sử dụng một bộ so sánh tùy chỉnh, nó không liên quan gì đến mã băm mặc định của đối tượng liên quan đến bộ so sánh mặc định , do đó bạn không thể sử dụng nó.
Peet Brits

170

Về tầm quan trọng của GetHashCode

Những người khác đã nhận xét về thực tế rằng bất kỳ IEqualityComparer<T>triển khai tùy chỉnh nào thực sự nên bao gồm một GetHashCodephương thức ; nhưng không ai bận tâm để giải thích tại sao trong bất kỳ chi tiết.

Đây là lý do tại sao. Câu hỏi của bạn đặc biệt đề cập đến các phương pháp mở rộng LINQ; gần như tất cả các mã này dựa vào mã băm để hoạt động chính xác, bởi vì chúng sử dụng các bảng băm trong nội bộ để đạt hiệu quả.

Lấy Distinctví dụ. Hãy xem xét ý nghĩa của phương pháp mở rộng này nếu tất cả những gì nó sử dụng là một Equalsphương pháp. Làm thế nào để bạn xác định xem một mục đã được quét theo trình tự nếu bạn chỉ có Equals? Bạn liệt kê toàn bộ bộ sưu tập các giá trị bạn đã xem và kiểm tra sự trùng khớp. Điều này sẽ dẫn đến Distinctviệc sử dụng thuật toán O (N 2 ) trong trường hợp xấu nhất thay vì thuật toán O (N)!

May mắn thay, đây không phải là trường hợp. Distinctkhông chỉ sử dụng Equals; nó sử dụng GetHashCodelà tốt. Trong thực tế, nó hoàn toàn không hoạt động đúng mà không có một IEqualityComparer<T>nguồn cung cấp phù hợpGetHashCode . Dưới đây là một ví dụ giả định minh họa điều này.

Nói rằng tôi có loại sau:

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

Bây giờ nói rằng tôi có một List<Value>và tôi muốn tìm tất cả các yếu tố với một tên riêng biệt. Đây là một trường hợp sử dụng hoàn hảo để Distinctsử dụng một bộ so sánh bình đẳng tùy chỉnh. Vì vậy, hãy sử dụng Comparer<T>lớp từ câu trả lời của Aku :

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

Bây giờ, nếu chúng ta có một loạt các Valuephần tử có cùng thuộc Nametính, tất cả chúng sẽ thu gọn thành một giá trị được trả về Distinct, phải không? Hãy xem nào...

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

Đầu ra:

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

Hmm, điều đó đã không làm việc, phải không?

Thế còn GroupBy? Hãy thử xem:

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

Đầu ra:

[KEY = 'x: 1346013431']
x: 1346013431
[KEY = 'x: 1388845617']
x: 1388845617
[KEY = 'x: 1576754134']
x: 1576754134
[KEY = 'x: 1104067189']
x: 1104067189
[KEY = 'x: 1144789201']
x: 1144789201
[KEY = 'x: 1862076501']
x: 1862076501
[KEY = 'x: 1573781440']
x: 1573781440
[KEY = 'x: 646797592']
x: 646797592
[KEY = 'x: 655632802']
x: 655632802
[KEY = 'x: 1206819377']
x: 1206819377

Một lần nữa: không hoạt động.

Nếu bạn nghĩ về nó, nó sẽ làm cho ý nghĩa đối với Distinctsử dụng một HashSet<T>(hoặc tương đương) trong nội bộ, và cho GroupByđến việc sử dụng một cái gì đó giống như một Dictionary<TKey, List<T>>nội bộ. Điều này có thể giải thích tại sao các phương pháp này không hoạt động? Chúng ta hãy cố gắng này:

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

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

Đầu ra:

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

Vâng ... bắt đầu có ý nghĩa?

Hy vọng từ những ví dụ này, rõ ràng tại sao bao gồm một sự phù hợp GetHashCodetrong bất kỳ IEqualityComparer<T>triển khai nào lại quan trọng đến vậy.


Câu trả lời gốc

Mở rộng câu trả lời của orip :

Có một vài cải tiến có thể được thực hiện ở đây.

  1. Đầu tiên, tôi sẽ Func<T, TKey>thay thế Func<T, object>; điều này sẽ ngăn quyền anh của các loại khóa giá trị trong thực tế keyExtractor.
  2. Thứ hai, tôi thực sự thêm một where TKey : IEquatable<TKey>ràng buộc; điều này sẽ ngăn quyền anh trong Equalscuộc gọi ( object.Equalslấy objecttham số; bạn cần IEquatable<TKey>triển khai để lấy TKeytham số mà không cần quyền anh). Rõ ràng điều này có thể đặt ra một hạn chế quá nghiêm trọng, vì vậy bạn có thể tạo một lớp cơ sở mà không có sự ràng buộc và một lớp dẫn xuất với nó.

Đây là mã kết quả có thể trông như thế nào:

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.EqualsPhương pháp của bạn có vẻ giống như KeyEqualityComparer.Equals. Liệu các TKey : IEquatable<TKey>ràng buộc làm cho TKey.Equalscông việc khác nhau?
Justin Morgan

2
@JustinMorgan: Có - trong trường hợp đầu tiên, vì TKeycó thể là bất kỳ loại tùy ý nào, trình biên dịch sẽ sử dụng phương thức ảo Object.Equalssẽ yêu cầu quyền anh của các tham số loại giá trị, ví dụ : int. Tuy nhiên, trong trường hợp sau, do TKeybị hạn chế thực hiện IEquatable<TKey>, TKey.Equalsphương thức sẽ được sử dụng mà sẽ không yêu cầu bất kỳ quyền anh nào.
Dan Tao

2
Rất thú vị, cảm ơn thông tin. Tôi không biết GetHashCode có những hàm ý LINQ này cho đến khi thấy những câu trả lời này. Tuyệt vời để biết để sử dụng trong tương lai.
Justin Morgan

1
@JohannesH: Có lẽ! Sẽ loại bỏ sự cần thiết cho StringKeyEqualityComparer<T, TKey>quá.
Dan Tao

1
+1 @DanTao: Cảm ơn vì đã thể hiện rất rõ lý do tại sao người ta không bao giờ nên bỏ qua mã băm khi xác định đẳng thức trong .Net.
Marcelo Cantos

118

Khi bạn muốn tùy chỉnh kiểm tra đẳng thức, 99% thời gian bạn quan tâm đến việc xác định các khóa để so sánh, chứ không phải so sánh chính nó.

Đây có thể là một giải pháp tao nhã (khái niệm từ phương pháp sắp xếp danh sách của Python ).

Sử dụng:

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

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

Cả KeyEqualityComparerlớp

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
Đây là nhiều hơn câu trả lời aku của.
SLaks

Chắc chắn là cách tiếp cận đúng. Theo tôi, có một vài cải tiến có thể được thực hiện trong câu trả lời của riêng tôi.
Dan Tao

1
Đây là mã rất thanh lịch, nhưng nó không trả lời câu hỏi, đó là lý do tại sao tôi chấp nhận câu trả lời của @ aku. Tôi muốn có một trình bao bọc cho Func <T, T, bool> và tôi không có yêu cầu trích xuất một khóa, vì khóa đã được tách ra trong Từ điển của tôi.
Marcelo Cantos

6
@Marcelo: Điều đó tốt, bạn có thể làm điều đó; nhưng lưu ý rằng nếu bạn định sử dụng phương pháp của @ aku, bạn thực sự nên thêm một Func<T, int>để cung cấp mã băm choT giá trị (như đã được đề xuất trong, ví dụ: câu trả lời của Ruben ). Mặt khác, việc IEqualityComparer<T>triển khai mà bạn để lại khá là hỏng, đặc biệt là liên quan đến tính hữu ích của nó trong các phương thức mở rộng LINQ. Xem câu trả lời của tôi cho một cuộc thảo luận về lý do tại sao điều này là.
Dan Tao

Điều này là tốt nhưng nếu khóa được chọn là một loại giá trị thì sẽ có quyền anh không cần thiết. Có lẽ sẽ tốt hơn nếu có TKey để xác định khóa.
Graham Ambrose

48

Tôi sợ rằng không có bao bì ngoài hộp như vậy. Tuy nhiên, không khó để tạo một:

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
Tuy nhiên, hãy cẩn thận với việc triển khai GetHashCode đó. Nếu bạn thực sự sẽ sử dụng nó trong một số loại bảng băm, bạn sẽ muốn thứ gì đó mạnh mẽ hơn một chút.
thecoop

46
mã này có một vấn đề nghiêm trọng! thật dễ dàng để đưa ra một lớp có hai đối tượng bằng nhau về so sánh này nhưng có mã băm khác nhau.
empi

10
Để khắc phục điều này, lớp cần một thành viên khác private readonly Func<T, int> _hashCodeResolvercũng phải được thông qua trong hàm tạo và được sử dụng trong GetHashCode(...)phương thức.
herzmeister

6
Tôi tò mò: Tại sao bạn sử dụng obj.ToString().ToLower().GetHashCode()thay vì obj.GetHashCode()?
Justin Morgan

3
Các vị trí trong khung IEqualityComparer<T>sử dụng băm thường xuyên đằng sau hậu trường (ví dụ: GroupBy, Distinc, Ngoại trừ, Tham gia, v.v. của LINQ) và hợp đồng MS liên quan đến băm bị phá vỡ trong triển khai này. Đoạn trích tài liệu của MS đây: "Cần phải triển khai để đảm bảo rằng nếu phương thức Equals trả về true cho hai đối tượng x và y, thì giá trị được trả về bởi phương thức GetHashCode cho x phải bằng giá trị được trả về cho y." Xem: msdn.microsoft.com/en-us/l Library / ms132155
devgeezer

22

Giống như câu trả lời của Dan Tao, nhưng với một vài cải tiến:

  1. Dựa vào EqualityComparer<>.Defaultđể thực hiện so sánh thực tế để nó tránh quyền anh đối với các loại giá trị structđã thực hiện IEquatable<>.

  2. Kể từ khi EqualityComparer<>.Defaultsử dụng, nó không phát nổnull.Equals(something) .

  3. Cung cấp bao bọc tĩnh xung quanh IEqualityComparer<> sẽ có một phương thức tĩnh để tạo phiên bản so sánh - giảm bớt cuộc gọi. Đối chiếu

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

    với

    new EqualityComparer<Person, int>(p => p.ID);
  4. Đã thêm quá tải để chỉ định IEqualityComparer<> cho khóa.

Lớp:

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

bạn có thể sử dụng nó như thế này:

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

Người là một lớp đơn giản:

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

3
+1 để cung cấp một triển khai cho phép bạn cung cấp một bộ so sánh cho khóa. Bên cạnh việc cho phép linh hoạt hơn, điều này cũng tránh các loại giá trị đấm bốc cho cả so sánh và băm.
devgeezer

2
Đây là câu trả lời xác thịt nhất ở đây. Tôi đã thêm một kiểm tra null là tốt. Hoàn thành.
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 );
    }
}

Với phần mở rộng: -

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 (người không còn tồn tại như bình luận này): Đã dọn sạch mã mà không có hành vi điều chỉnh (và + 1'd). Đã thêm Riff tại stackoverflow.com/questions/98033/
Mạnh

6

Câu trả lời của orip là tuyệt vời.

Đây là một phương pháp mở rộng để làm cho nó dễ dàng hơn nữa:

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

Tôi sẽ trả lời câu hỏi của riêng tôi. Để coi Từ điển là tập hợp, phương pháp đơn giản nhất dường như là áp dụng các thao tác tập hợp cho dict.Keys, sau đó chuyển đổi trở lại Từ điển với Enumerable.ToDixi (...).


2

Việc triển khai tại (văn bản tiếng Đức) Triển khai IEqualityCompare với biểu thức lambda quan tâm đến các giá trị null và sử dụng các phương thức mở rộng để tạo IEqualityComparer.

Để tạo một IEqualityComparer trong liên minh Linq, bạn chỉ cần viết

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

Bộ so sánh:

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

Bạn cũng cần thêm một phương thức mở rộng để hỗ trợ suy luận kiểu

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

Chỉ cần một tối ưu hóa: Chúng ta có thể sử dụng EqualityComparer bên ngoài để so sánh giá trị, thay vì ủy thác nó.

Điều này cũng sẽ làm cho việc triển khai sạch hơn khi logic so sánh thực tế hiện nằm trong GetHashCode () và Equals () mà bạn có thể đã bị quá tải.

Đây là mã:

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

Đừng quên quá tải các phương thức GetHashCode () và Equals () trên đối tượng của bạn.

Bài đăng này đã giúp tôi: c # so sánh hai giá trị chung

Sushil


1
NB cùng một vấn đề như được xác định trong nhận xét về stackoverflow.com/questions/98033/ từ - CANT giả sử obj.GetHashCode () có ý nghĩa
Ruben Bartelink

4
Tôi không hiểu mục đích của cái này. Bạn đã tạo một bộ so sánh bằng tương đương với bộ so sánh đẳng thức mặc định. Vậy tại sao bạn không sử dụng nó trực tiếp?
CodeInChaos

1

câu trả lời của orip là tuyệt vời. Mở rộng câu trả lời của orip:

tôi nghĩ rằng khóa của giải pháp là sử dụng "Phương thức mở rộng" để chuyển "loại ẩn danh".

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

Sử dụng:

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

Điều này cho phép chọn một thuộc tính với lambda như thế này: .Select(y => y.Article).Distinct(x => x.ArticleID);


-2

Tôi không biết về một lớp hiện có nhưng đại loại như:

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

Lưu ý: Tôi chưa thực sự biên dịch và chạy nó, vì vậy có thể có lỗi đánh máy hoặc lỗi khác.


1
NB cùng một vấn đề như được xác định trong nhận xét về stackoverflow.com/questions/98033/ từ - CANT giả sử obj.GetHashCode () có ý nghĩa
Ruben Bartelink
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.