C# 使用 linq 从两个对象列表创建一个列表

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/720609/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 22:24:35  来源:igfitidea点击:

Create a list from two object lists with linq

c#linq

提问by

I have the following situation

我有以下情况

class Person
{
    string Name;
    int Value;
    int Change;
}

List<Person> list1;
List<Person> list2;

I need to combine the 2 lists into a new List<Person>in case it's the same person the combine record would have that name, value of the person in list2, change would be the value of list2 - the value of list1. Change is 0 if no duplicate

我需要将 2 个列表组合成一个新的List<Person>,以防合并记录具有该名称的同一个人,list2 中该人的值,更改将是 list2 的值 - list1 的值。如果没有重复,则更改为 0

回答by Richard

There are a few pieces to doing this, assuming each list does not contain duplicates, Name is a unique identifier, and neither list is ordered.

有几个部分可以做到这一点,假设每个列表不包含重复项,Name 是一个唯一标识符,并且两个列表都没有排序。

First create an append extension method to get a single list:

首先创建一个 append 扩展方法来获取单个列表:

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

Thus can get a single list:

因此可以得到一个列表:

var oneList = list1.Append(list2);

Then group on name

然后按名称分组

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

Then can process each group with a helper to process one group at a time

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

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

Which can be applied to each element of grouped:

这可以应用于 的每个元素grouped

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

(Warning: untested.)

(警告:未经测试。)

回答by Amy B

You need something like a full outer join. System.Linq.Enumerable has no method that implements a full outer join, so we have to do it ourselves.

你需要一个完整的外部连接之类的东西。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();

回答by Koen Zomers

This can easily be done by using the Linq extension method Union. For example:

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

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

This will return a List in which the two lists are merged and doubles are removed. If you don't specify a comparer in the Union extension method like in my example, it will use the default Equals and GetHashCode methods in your Person class. If you for example want to compare persons by comparing their Name property, you must override these methods to perform the comparison yourself. Check the following code sample to accomplish that. You must add this code to your Person class.

这将返回一个列表,其中合并了两个列表并删除了双打。如果您没有像我的示例那样在 Union 扩展方法中指定比较器,它将在您的 Person 类中使用默认的 Equals 和 GetHashCode 方法。例如,如果您想通过比较 Name 属性来比较人员,则必须重写这些方法以自己执行比较。检查以下代码示例以完成该操作。您必须将此代码添加到您的 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);
}

If you don't want to set the default Equals method of your Person class to always use the Name to compare two objects, you can also write a comparer class which uses the IEqualityComparer interface. You can then provide this comparer as the second parameter in the Linq extension Union method. More information on how to write such a comparer method can be found on http://msdn.microsoft.com/en-us/library/system.collections.iequalitycomparer.aspx

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

回答by Mike Goatly

I noticed that this question was not marked as answered after 2 years - I think the closest answer is Richards, but it can be simplified quite a lot to this:

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

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

Although this won't errorin the case where you have duplicate names in either set.

尽管在任何一组中都有重复名称的情况下这不会出错

Some other answers have suggested using unioning - this is definitely not the way to go as it will only get you a distinct list, without doing the combining.

其他一些答案建议使用联合 - 这绝对不是要走的路,因为它只会为您提供一个不同的列表,而无需进行组合。

回答by pungggi

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

回答by J4N

Why you don't just use Concat?

为什么你不只是使用Concat

Concat is a part of linq and more efficient than doing an AddRange()

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

in your case:

在你的情况下:

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

回答by Sean Reid

Does the following code work for your problem? I've used a foreach with a bit of linq inside to do the combining of lists and assumed that people are equal if their names match, and it seems to print the expected values out when run. Resharper doesn't offer any suggestions to convert the foreach into linq so this is probably as good as it'll get doing it this way.

以下代码是否适用于您的问题?我使用了一个里面有一点 linq 的 foreach 来组合列表,并假设人们在名字匹配时是平等的,并且它似乎在运行时打印出预期值。Resharper 没有提供任何将 foreach 转换为 linq 的建议,因此这可能与它以这种方式进行的一样好。

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

回答by Alper ?ald?rak

This is Linq

这是林克

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

This is Normaly (AddRange)

这是正常的(AddRange)

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

This is Normaly (Foreach)

这是正常的(Foreach)

var mergedList=new List<Person>();

foreach(var item in list1)
{
    mergedList.Add(item);
}
foreach(var item in list2)
{
     mergedList.Add(item);
}

This is Normaly (Foreach-Dublice)

这是正常的(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);
   }
}