Удаление дубликатов при слиянии списков с помощью Union в LINQ

Я пытаюсь объединить два списка, используя list.Union в LinqPad, но я не могу заставить его работать и хочу проверить, что мое понимание верное.

Учитывая этот простой класс:

public class Test 
{
   public int Id { get; set;}
   public int field1 { get; set; }

   public bool Equals(Test other)
   {        
      return this.Id.Equals(other.Id);
   }
}

И два списка заполнены следующим образом:

List<Test> list = new List<Test>();
list.Add( new Test { Id = 1, field1 = 1});
list.Add( new Test { Id = 1, field1 = 2});
list.Add( new Test { Id = 2, field1 = 3});
list.Add( new Test { Id = 2, field1 = 4});

List<Test> list2 = new List<Test>();
list2.Add( new Test { Id = 1, field1 = 1});
list2.Add( new Test { Id = 1, field1 = 2});
list2.Add( new Test { Id = 2, field1 = 3});
list2.Add( new Test { Id = 2, field1 = 4});

Затем я попытаюсь: var mergedList = list.Union(list2).ToList(); и вывести данные с помощью простого цикла foreach и получить этот вывод:

ID: 1 -------- 1
ID: 1 -------- 2
ID: 2 -------- 3
ID: 2 -------- 4
ID: 1 -------- 1
ID: 1 -------- 2
ID: 2 -------- 3
ID: 2 -------- 4

У меня создалось впечатление, что Union должен удалить возвращаемые дубликаты:

ID: 1 -------- 1
ID: 1 -------- 2
ID: 2 -------- 3
ID: 2 -------- 4

Я что-то делаю неправильно или неправильно понял?

Кроме того, должно ли оно работать без явного переопределения метода Equals в классе Test?

Спасибо

Ответ 1

В вашем случае вы просто определяете какой-то метод, о котором LINQ ничего не знает. Это похоже на метод создания bool HeyEquateMeWith(Test other) и ожидать, что LINQ будет вызывать его при выполнении заданий.

Вам нужно определить свой класс следующим образом ( переопределить Object Equals и GetHashCode):

public class Test 
{
   public int Id { get; set;}
   public int field1 { get; set; }  

   public override bool Equals(object other) //note parameter is of type object
   {        
        Test t = other as Test;
        return (t != null) ? Id.Equals(t.Id) : false;
   }

   public override int GetHashCode()
   {
        return Id.GetHashCode();
   }
}

Теперь Union вызовет ваши переопределенные методы Equals и GetHashCode. Также вы должны ВСЕГДА отменить GetHashCode при переопределении метода Equals.

Ответ 2

Вы можете создать класс, реализующий

IEqualityComparer<Test>

Этот класс определяет Equals и GetHashCode После этого вы можете передать этот компаньон вам методом Union Точно так же:

public class MyComparer:IEqualityComparer<Test>{
//Equals and GetHashCode
}

var mergedList = list.Union(list2, new MyComparer()).ToList();

Ответ 3

Вы можете попробовать что-то подобное, если не довольны стандартным компаратором (который, в свою очередь, использует метод GetHashCode, как упоминал @IlyaIvanov):

// get all items that "other than in first list", so Where() and Any() are our filtering expressions
var delta = list2.Where(x2 => !list.Any(x1 => (x1.Id == x2.Id) && (x1.field1 == x2.field1)));

// now let merge two enumerables that have nothing "equal" between them
var merged = list.Union(delta).ToList();