Crea una lista da due elenchi di oggetti con linq

Ho la seguente situazione

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

Ho bisogno di combinare le 2 liste in una nuova List nel caso in cui sia la stessa persona che il record di combinazione avrebbe quel nome, valore della persona in list2, il cambiamento sarebbe il valore di list2 – il valore di list1. Il cambiamento è 0 se non c’è duplicato

Questo può essere fatto facilmente utilizzando il metodo di estensione Linq Union. Per esempio:

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

Ciò restituirà una lista in cui i due elenchi vengono uniti e i doppi vengono rimossi. Se non si specifica un confronto nel metodo di estensione Unione come nel mio esempio, utilizzerà i metodi Equals e GetHashCode predefiniti nella class Person. Se ad esempio vuoi confrontare le persone confrontando la loro proprietà Name, devi eseguire l’override di questi metodi per eseguire il confronto tu stesso. Controlla il seguente codice di esempio per farlo. Devi aggiungere questo codice alla tua class Person.

 ///  /// Checks if the provided object is equal to the current Person ///  /// Object to compare to the current Person /// True if equal, false if not 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); } ///  /// Returns an identifier for this instance ///  public override int GetHashCode() { return Name.GetHashCode(); } ///  /// Checks if the provided Person is equal to the current Person ///  /// Person to compare to the current person /// True if equal, false if not 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); } 

Se non si desidera impostare il metodo Equals predefinito della class Person per utilizzare sempre il nome per confrontare due oggetti, è anche ansible scrivere una class di confronto che utilizza l’interfaccia IEqualityComparer. È quindi ansible fornire questo confronto come secondo parametro nel metodo Unione estensione Linq. Ulteriori informazioni su come scrivere un tale metodo di confronto possono essere trovate su http://msdn.microsoft.com/en-us/library/system.collections.iequalitycomparer.aspx

Perché non usi solo Concat ?

Concat è una parte di linq e più efficiente di un AddRange()

nel tuo caso:

 List list1 = ... List list2 = ... List total = list1.Concat(list2); 

Ho notato che questa domanda non è stata contrassegnata come risposta dopo 2 anni: penso che la risposta più vicina sia Richards, ma può essere semplificata parecchio:

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

Anche se questo non sarà un errore nel caso in cui si hanno nomi duplicati in entrambi i set.

Alcune altre risposte hanno suggerito di utilizzare l’unione: non è assolutamente la strada da percorrere, in quanto ti consente di ottenere solo una lista distinta, senza la combinazione.

Ci sono alcuni pezzi per fare ciò, supponendo che ogni lista non contenga duplicati, Nome è un identificativo univoco e nessuna lista è ordinata.

Prima crea un metodo di estensione append per ottenere una singola lista:

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

Quindi puoi ottenere una singola lista:

 var oneList = list1.Append(list2); 

Quindi raggruppa il nome

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

Quindi può elaborare ciascun gruppo con un helper per elaborare un gruppo alla volta

 public Person MergePersonGroup(IGrouping 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); } } 

Che può essere applicato a ciascun elemento di grouped :

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

(Attenzione: non testato.)

Questo è Linq

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

Questo è Normaly (AddRange)

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

Questo è Normaly (Foreach)

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

Questo è Normaly (Foreach-Dublice)

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

Hai bisogno di qualcosa come una giuntura esterna completa. System.Linq.Enumerable non ha alcun metodo che implementa un join esterno completo, quindi dobbiamo farlo da soli.

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

Il seguente codice funziona per il tuo problema? Ho usato un foreach con un po ‘di linq dentro per fare la combinazione di liste e presume che le persone siano uguali se i loro nomi corrispondono, e sembra stampare i valori attesi quando vengono eseguiti. Resharper non offre alcun suggerimento per convertire il foreach in linq, quindi probabilmente è così buono come lo farò in questo modo.

 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 list1 = new List { new Person("a", 1), new Person("b", 2), new Person("c", 3), new Person("d", 4) }; List list2 = new List { new Person("a", 4), new Person("b", 5), new Person("e", 6), new Person("f", 7) }; List 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 customers = GetCustomerList(); List 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); } }