有一堂课
public class DB
{
public string ID{get;set;}
public string Name{get;set;}
...
}
有两个列表。如何获取两个列表的唯一值?对于普通列表,它是这样完成的
var A = new List<int>() { 1,2,3,4 };
var B = new List<int>() { 1, 5, 6, 7 };
var a= A.Except(B).ToList(); //{ 2,3,4 }
var b= B.Except(A).ToList(); //{ 5,6,7 }
var abint= a.AddRange(b);
对于普通的
List,这更容易通过Union:该方法的文档说明了如何将其应用于您的对象:
如果对于数字来说,相同的数字是什么是非常明显的,那么对于类实例
DB,它就不再那么明显了。如果您的类
DB实现了一个接口IEquatable<DB>并定义了方法Equals和GethashCode,那么您的对象相等性很可能与相等性相同Equals,并且您可以使用默认语言环境答案中的实现。但是,如果您不能或不想实施
IEquatable<DB>怎么办?例如,您的类是否属于第 3 方库,或者是实现接口没有多大意义的 DTO 类?仅仅为了创建一个列表而更改类的实现并不是很正确,尤其是因为您可能在其他地方需要它Equals来获取其他属性。在这种情况下,您必须弄清楚对我们来说什么是相等的实例。
默认情况下,仅匹配的实例被认为是相等的。例如,如果您创建
那么
o1它们o2将不会被视为相等,只有匹配的对象才会被视为相等。如果这是您需要的,那么您a.Union(b).ToList()无需任何更改即可使用它。例如,如果您的对象来自数据库,那么对您来说,最有可能的是那些具有相同
Id. 为此,您需要按 比较对象Id。怎么做?您可以使用采用外部比较器类型的重载
Union,您可以将比较器传递给该特殊情况。IEqualityComparer<DB>这样的比较器可以手动编写:
你的代码将是这样的
a.Union(b, new DBComparer()).ToList():可以编写一个可以重用的更通用的比较器:
你的代码将是这样的
a.Union(b, new ProjectingComparer<DB, string>(db => db.Id)).ToList():在未来的 .NET 6 中,将有一个方法
UnionBy可以完全满足我们的需要,因此您的示例将被简单地编写为a.UnionBy(b, db => db.Id)没有任何辅助构造。现在,您可以使用 package
MoreLinq,虽然没有方法UnionBy,但是有一种方法DistinctBy可以很容易地实现所需的功能:a.Concat(b).DistinctBy(db => db.ID).ToList().顺便说一句,很
DistinctBy容易自己写:另一种(效率较低的)模拟方法
DistinctBy是使用GroupBy:a.Concat(b).GroupBy(db => db.Id).Select(g => g.First())。但是,如果您不想为一个字段设置相等性
Id,而是为多个字段设置相等性怎么办?原则上,同样的方法有效,只是会有其他的 lambda,因为你可以使用元组将几个字段值组合成一个。得到a.Union(b).DistinctBy(db => (db.ID, db.Name)).ToList()它或那里a.Union(b, new ProjectingComparer<DB, (string, string)>(db => (db.ID, db.Name))).ToList()。好吧,这个类DBComparer可以以一种明显的方式重写。