用linq从两个对象列表中创build一个列表

我有以下情况

class Person { string Name; int Value; int Change; } List<Person> list1; List<Person> list2; 

我需要将2个列表合并到一个新的List<Person>中,以便合并logging具有该名称的同一个人, List<Person> 2中的人员的值,更改将是列表2的值 – 列表1的值。 如果不重复,则更改为0

这可以通过使用Linq扩展方法Union轻松完成。 例如:

 var mergedList = list1.Union(list2).ToList(); 

这将返回一个列表,其中两个列表被合并,双打被删除。 如果你没有像在我的例子中那样在联合扩展方法中指定一个比较器,它将在你的Person类中使用默认的Equals和GetHashCode方法。 例如,如果您想通过比较其名称属性来比较人员,则必须重写这些方法以自行执行比较。 检查下面的代码示例来完成。 您必须将此代码添加到您的Person类。

 /// <summary> /// Checks if the provided object is equal to the current Person /// </summary> /// <param name="obj">Object to compare to the current Person</param> /// <returns>True if equal, false if not</returns> public override bool Equals(object obj) { // Try to cast the object to compare to to be a Person var person = obj as Person; return Equals(person); } /// <summary> /// Returns an identifier for this instance /// </summary> public override int GetHashCode() { return Name.GetHashCode(); } /// <summary> /// Checks if the provided Person is equal to the current Person /// </summary> /// <param name="personToCompareTo">Person to compare to the current person</param> /// <returns>True if equal, false if not</returns> public bool Equals(Person personToCompareTo) { // Check if person is being compared to a non person. In that case always return false. if (personToCompareTo == null) return false; // If the person to compare to does not have a Name assigned yet, we can't define if it's the same. Return false. if (string.IsNullOrEmpty(personToCompareTo.Name) return false; // Check if both person objects contain the same Name. In that case they're assumed equal. return Name.Equals(personToCompareTo.Name); } 

如果您不希望将Person类的默认Equals方法设置为始终使用Name来比较两个对象,则还可以编写一个使用IEqualityComparer接口的comparer类。 然后,您可以将此比较器作为Linq扩展联合方法中的第二个参数。 有关如何编写这种比较器方法的更多信息,请访问http://msdn.microsoft.com/zh-CN/library/system.collections.iequalitycomparer.aspx

为什么你不使用Concat

Concat是linq的一部分,比AddRange()更有效率

在你的情况下:

 List<Person> list1 = ... List<Person> list2 = ... List<Person> total = list1.Concat(list2); 

我注意到这个问题在两年之后没有被标记为答案 – 我认为最接近的答案是理查兹,但是可以简化很多:

 list1.Concat(list2) .ToLookup(p => p.Name) .Select(g => g.Aggregate((p1, p2) => new Person { Name = p1.Name, Value = p1.Value, Change = p2.Value - p1.Value })); 

虽然在任何一组中都有重复名称的情况下,这不会造成错误

一些其他的答案build议使用联合 – 这绝对不是要走的路,因为它只会给你一个明确的名单,而不做组合。

有几件要做到这一点,假设每个列表不包含重复,名称是一个唯一的标识符,这两个列表都没有sorting。

首先创build一个附加扩展方法来获取单个列表:

 static class Ext { public static IEnumerable<T> Append(this IEnumerable<T> source, IEnumerable<T> second) { foreach (T t in source) { yield return t; } foreach (T t in second) { yield return t; } } } 

因此可以得到一个单子:

 var oneList = list1.Append(list2); 

然后组名

 var grouped = oneList.Group(p => p.Name); 

然后可以使用助手处理每个组,一次处理一个组

 public Person MergePersonGroup(IGrouping<string, Person> pGroup) { var l = pGroup.ToList(); // Avoid multiple enumeration. var first = l.First(); var result = new Person { Name = first.Name, Value = first.Value }; if (l.Count() == 1) { return result; } else if (l.Count() == 2) { result.Change = first.Value - l.Last().Value; return result; } else { throw new ApplicationException("Too many " + result.Name); } } 

哪些可以应用于grouped每个元素:

 var finalResult = grouped.Select(g => MergePersonGroup(g)); 

(警告:未经testing。)

这是Linq

 var mergedList = list1.Union(list2).ToList(); 

这是Normaly(AddRange)

 var mergedList=new List<Person>(); mergeList.AddRange(list1); mergeList.AddRange(list2); 

这是Normaly(Foreach)

 var mergedList=new List<Person>(); foreach(var item in list1) { mergedList.Add(item); } foreach(var item in list2) { mergedList.Add(item); } 

这是Normaly(Foreach-Dublice)

 var mergedList=new List<Person>(); foreach(var item in list1) { mergedList.Add(item); } foreach(var item in list2) { if(!mergedList.Contains(item)) { mergedList.Add(item); } } 

你需要像完整的外连接。 System.Linq.Enumerable没有实现完全外连接的方法,所以我们必须自己做。

 var dict1 = list1.ToDictionary(l1 => l1.Name); var dict2 = list2.ToDictionary(l2 => l2.Name); //get the full list of names. var names = dict1.Keys.Union(dict2.Keys).ToList(); //produce results var result = names .Select( name => { Person p1 = dict1.ContainsKey(name) ? dict1[name] : null; Person p2 = dict2.ContainsKey(name) ? dict2[name] : null; //left only if (p2 == null) { p1.Change = 0; return p1; } //right only if (p1 == null) { p2.Change = 0; return p2; } //both p2.Change = p2.Value - p1.Value; return p2; }).ToList(); 

以下代码是否适用于您的问题? 我用一个linq里面的foreach来做列表的组合,并且假定人名是相等的,如果他们的名字匹配的话,似乎在运行时打印出预期的值。 Resharper没有提供任何将foreach转换为linq的build议,所以这可能会像这样做。

 public class Person { public string Name { get; set; } public int Value { get; set; } public int Change { get; set; } public Person(string name, int value) { Name = name; Value = value; Change = 0; } } class Program { static void Main(string[] args) { List<Person> list1 = new List<Person> { new Person("a", 1), new Person("b", 2), new Person("c", 3), new Person("d", 4) }; List<Person> list2 = new List<Person> { new Person("a", 4), new Person("b", 5), new Person("e", 6), new Person("f", 7) }; List<Person> list3 = list2.ToList(); foreach (var person in list1) { var existingPerson = list3.FirstOrDefault(x => x.Name == person.Name); if (existingPerson != null) { existingPerson.Change = existingPerson.Value - person.Value; } else { list3.Add(person); } } foreach (var person in list3) { Console.WriteLine("{0} {1} {2} ", person.Name,person.Value,person.Change); } Console.Read(); } } 
 public void Linq95() { List<Customer> customers = GetCustomerList(); List<Product> products = GetProductList(); var customerNames = from c in customers select c.CompanyName; var productNames = from p in products select p.ProductName; var allNames = customerNames.Concat(productNames); Console.WriteLine("Customer and product names:"); foreach (var n in allNames) { Console.WriteLine(n); } }