22

I have 2 List<object>. The first one, lets call it ListA is more like a complete list and the second one ListB is a modified list. Now what I want to do is to modify ListA with ListB. Is this doable and how can I do it. This is what I have so far but doesn't work:

var ListB = _repository.Get(m => m.Approved == true).ToList();
foreach (var x in ListB)
{
  ListA.Where(d => d.Name == x.Name).First() = x;
}

return ListA;

EDIT: Visual Presentation to describe what 'modify' means in my situation

ListA
Id     Name      Age
1     John       14
2     Mark       15
3     Luke       13
4     Matthew    18

ListB
Id     Name      Age
2     Mark       0
4     Matthew    99

After 'modifying' it, ListA should look like:

ListA
Id     Name      Age
1     John       14
2     Mark       0
3     Luke       13
4     Matthew    99
super-user
  • 1,017
  • 4
  • 17
  • 41
  • 3
    You'll have to define what "modify" means in this case – Sami Kuhmonen Apr 10 '16 at 12:32
  • 1
    @SamiKuhmonen thank you, please see my edit. – super-user Apr 10 '16 at 12:38
  • well, you are changing ListA, but you actually returned ListB (from quick look) – ArielB Apr 10 '16 at 12:40
  • @ArielB thank you but that is a typo error. it still doesnt work – super-user Apr 10 '16 at 12:43
  • 1
    yes, i now checked it. your issue is that you're returning an item from the list, when it's returned via "First" - you get a reference to it, and even if you would change the value - it wouldnt be reflected in the list. you do have to go to that specific place in the list and replace the value. – ArielB Apr 10 '16 at 12:46
  • You can't modify the list that you're iterating through in a foreach statement which is why your code isn't working. Please clarify that you're trying to achieve and we'll see if we can help out further – Oliver Apr 10 '16 at 12:49
  • 1
    @Oliver he's not modifying the list he iterates on, he's using LINQ to get a specific item from the list, changing it means nothing to the list. (only if changing values inside) - i'd do some kind of "Copy" code of the data he has – ArielB Apr 10 '16 at 12:51

5 Answers5

24

As I consider, you want to update only an age. Also you don't need to use Where().First() you can use just First().

foreach (var x in ListB)
{
    var itemToChange = ListA.First(d => d.Name == x.Name).Age = x.Age;
}

If you are not sure, that item exists in ListA you should use FirstOrDefault() and if statement to check it.

foreach (var x in ListB)
{
    var itemToChange = ListA.FirstOrDefault(d => d.Name == x.Name);
    if (itemToChange != null)
         itemToChange.Age = x.Age;
}
Valentin
  • 5,380
  • 2
  • 24
  • 38
  • is there a way i can update all properties at once, I mean how can I update it something like `itemToChange = x`, impossible? – super-user Apr 10 '16 at 12:52
  • @super-user you have to implement copy or deep copy method, as everyone else suggest. – Valentin Apr 10 '16 at 13:02
  • @super-user it's not recommended, as if you have a class as a data member - you will do a shallow copy of it meaning, 2 different classes will reference to the same object. – ArielB Apr 10 '16 at 13:26
5

You could remove all elements of ListB from ListA based on Id, add ListB to ListA and then sort using Id.

var newlist = ListA.Where(s => !ListB.Any(p => p.Id == s.Id)).ToList();
newlist.AddRange(ListB);
ListA = newlist.OrderBy(o => o.Id).ToList();
Superman
  • 159
  • 1
  • 6
4

Where and First return IEnumerable - you can modify only node of the list, but not reassign.

option 0 - generic approach

using System.Collections.Generic;

//...

   var itemToUpdate = ListA.FirstOrDefault(d => d.Name == x.Name);
   if (itemToUpdate != null) {
       ListA[ListA.IndexOf(itemToUpdate)] = x;
   }

option 1 - implement the update method or perform field update manually

ListA.First(d => d.Name == x.Name).Update(x);
Andrey Ershov
  • 1,773
  • 1
  • 16
  • 26
  • what should I reference to use IndexOf() or Update(), both doesn't work on me – super-user Apr 10 '16 at 12:49
  • let's call your class that you're using in the list "Person", go to the "Person" class and add a "Copy(Person p)" method. then use a shallow/deep copy to that object. and you're done. and there's no IndexOf because IEnumerable doesn't have an index of - it's a List method (make sure ListA is really List), and Update is something you must write – ArielB Apr 10 '16 at 12:52
  • IndexOf is there for List<> class, and Update is a method, that you should implement, or other users wrote - just change the Age field. Update method will be in handy, if you have 10-20 fields. – Andrey Ershov Apr 10 '16 at 12:56
  • If no item is found it will throw a `InvalidOperationException`. A more safe solution would be to use `FirstOrDefault` and check for null before making any further operations. – Hypnobrew Apr 10 '16 at 12:58
  • @Merryweather i am not even going into using a Dictionary to search for elements :) – Andrey Ershov Apr 10 '16 at 12:59
3

to elaborate aershov's answer:

ListA.Where(d => d.Name == x.Name).First().CopyFrom(x);

then in your Person class:

public class Person
{
   // ... Name, Id, Age properties...

   public void CopyFrom(Person p)
   {
      this.Name = p.Name;
      this.Id = p.Id;
      this.Age = p.Age;
   }
}

of course check nulls and everything.

ArielB
  • 1,184
  • 2
  • 11
  • 36
0

You could also use the Union method with an IEqualityComparer:

var newList = ListB.Union(ListA, new PersonEqualityComparer());

class PersonEqualityComparer : IEqualityComparer<Person>
{
    public bool Equals(Person person1, Person person2)
    {
        if (person1 == null && person2 == null)
            return true;
        else if ((person1 != null && person2 == null) ||
                (person1 == null && person2 != null))
            return false;

        return person1.Id.Equals(person2.Id);
    }

    public int GetHashCode(Person item)
    {
        return item.Id.GetHashCode();
    }
}