Search code examples
c#.netlistclone

Update list without modifying the parent


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).


Solution

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