Home > Net >  Update list without modifying the parent
Update list without modifying the parent

Time:09-21

I have an in memory list, from which I get some rows and update a field. I can't understand why the parent list is also updated. I used ToList(), Range(), new List<type>(filteredRows), nothing works. I don't want to create another list and just push item by item, because the parent is huge and can affect the performance.

    private readonly IServiceScopeFactory scopeFactory;

    private List<Row> rows= new List<Row>();

    public InMemoryData(IServiceScopeFactory scopeFactory)
    {
        this.scopeFactory = scopeFactory;
    }

the original rows looks like below:

    { Name: 'Team A', Position: 1, CountryId: 1},
    { Name: 'Team B', Position: 2, CountryId: 2},
    { Name: 'Team C', Position: 3, CountryId: 1},
    { Name: 'Team D', Position: 4, CountryId: 1}

The problem is in the following method:

public List<Row> GetFiltereRows(int? countryId = null) {
   if (countryId != null) {
     var filteredRows = rows
        .Where(x => x.CountryId == (int)countryId)
        .ToList();
     
     var position = 0;
     return filteredRows.Select(x => {
        // here, also the entity "x" in the parent list is updated
        x.Position =   position; 
        return x;
     }).ToList();
   }

   return rows;    
}

When I filter the data by CountryId 1, I want that Team C to have Position 2 and Team D to have Position 3 (to recalculate positions only for selected country).

I think I need to set the rows list as readonly, or to create clones from it.

I can't understand why for another case, the clone works:

var filteredRows = rows;
if (countryId != null) {
  filteredRows = filteredRows.Where(x => x.CountryId == countryId ).ToList();
}

here, even if the filteredRows is declared as rows, this will not update the original list, filtering it. I know something about immutability from another languages, due of that I'm thinking that I need to do theoriginal list immutable, to use only copies of it in another methods. But also, somethimes I need to refresh the original list, so should still be able to update it (or recreate).

CodePudding user response:

I used ToList(), Range(), new List(filteredRows), nothing works.

new List<Type>(filteredRows) will create another reference pointing to the same list, it won't create a new list and clone the items in the original (parent) list.

.ToList()? Same thing, Actually, take a look at .ToList()'s implementation:

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source) => source != null ? new List<TSource>(source) : throw Error.ArgumentNull(nameof (source));

It's just the same as new List<Type>(filteredRows), but throws an exception if filteredRows is null!

"// here, also the entity "x" in the parent list is updated" this is normal

As stated, you are not deep-cloning the original items, so any modification to the filtered item is a direct modification to the original item.

I can think of two choices to overcome this issue:

  1. After filtering rows by countryId, you can tell how long is filteredRows, so maybe it won't affect the performance if you deep-clone it
return filteredRows.Select(x => {
    var xClone = new Row(x); // deep clone
    xClone.Position =   position; 
    return xClone;
 }).ToList();
  1. Or, you would write new function to reverse the edits made to the original list at some point after using the returned value from GetFiltereRows..
return filteredRows.Select(x => {
    x.OriginalPosition = x.Position;
    x.Position =   position; 
    return x;
 }).ToList();

Usage:

var filteredRows = GetFiltereRows(rows);
// use the filteredRows
RefreshRows();

void RefreshRows(){
   foreach(var item in rows) {
      item.Position = item.OriginalPosition;
      item.OriginalPosition = -1;
   }
}

CodePudding user response:

Even if you were to create a new list and push item by item, you would have the same issue.

This is because each row (your object with Name, Position, and CountryId) is a reference type as well. So even if you make a brand new list, that new list will still point to the same object in the other list. This is why you see both lists being updated, even after you successfully made a new list.

Your code would work as you intended if your list was composed of a value type, such as List<int>.

The only way I know how to fix this is to make a Row class and have it conform to ICloneable, like this:

class Row: ICloneable
{
    public String Name;
    public int Position;
    public int CountryId;

    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

Unfortunately, you would have to call Clone() on each row in your list, which will affect performance.

If anyone else has a better way, feel free to add on.

  • Related