使用LINQ中的Union合并列表时去除重复项

17

我正在尝试使用LinqPad中的list.Union合并两个列表,但是我无法使其正常工作,想要确认我的理解是否正确。

鉴于这个简单的类:

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

我是在做错了什么还是理解错了?

此外,在Test类中,是否应该不需要显式重写Equals方法就可以正常工作?

谢谢


1
你应该阅读此文档页面 - Jon
4个回答

20

在您的情况下,您只是定义了一些LINQ不知道的方法。这就像创建一个方法bool HeyEquateMeWith(Test other)并期望LINQ在执行设置操作时调用它。

您需要按照以下方式定义您的类(覆盖 ObjectEqualsGetHashCode 方法):

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将调用您重写的EqualsGetHashCode方法。 当您重写Equals方法时,您应该总是重写GetHashCode方法。


谢谢。是的,我本以为可以留下GetHashCode作为一个例子,但它是必需的。默认比较器对于Union如何工作?还是我总是需要重写Equals和GetHashCode? - davy
2
@davy默认比较器将查看对象身份(与==运算符相同的功能)。如果您将您的“Test”类更改为“结构体”,则默认的相等性将具有值语义(比较两个结构体的内容,包括“Id”和“field1”)。 - Ilya Ivanov

2
如果不满意默认的比较器(它又使用了@IlyaIvanov提到的GetHashCode方法),可以尝试类似以下的方式:
// 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();

1
你可以创建一个实现该类的类。
IEqualityComparer<Test>

这个类是否定义了Equals和GetHashCode方法?如果是,你可以将此比较器传递给Union方法。就像这样:

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

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

这个方法适用于LINQ to Objects,但不支持LINQ to Entities。我是通过吃亏才发现的。 - Suncat2000

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接