Grim0419
Grim0419

Reputation: 19

C#: Initial assignment of a property to a variable which holds a List<string> gets updated when later on RemoveAll is used on the same variable

We are performing a migration and we are supposed to log the final report for the same. To achieve the same, I have a class which has multiple properties that holds List of Ids(string) such as All the Ids involved in Migration, All the Ids which are Migrated etc. While doing the same what I came across was that my previous assignment was getting updated when I performed a RemoveAll on a variable that holds all the List of Ids, to better understand, PFB an example:

public class Report {
  public IdsInfo AllIds {get; set;}
  public IdsInfo MigratedIds {get; set;}
  public IdsInfo FailedIds {get; set;}
}

public class IdsInfo {
  public int Count{get; set;}
  public List<string> Ids {get; set;}
  
  public IdsInfo(List<string> ids) {
    Count = ids.Count;
    Ids = ids;
  }
}

//An example of the Method that does the Migration
public Report Migrate(string criteria) {
  Report report = new Report();
  //Assume below is a DB Call
  List<string> allIds = db.getData(criteria).Select(x=>x.id).toList();//Assume Count = 1000
  report.AllIds = new IdsInfo(allIds);

  //Assume below is a DB Call to check whether Ids are already migrated
  List<string> migratedIds = db.getData(x=>allIds.contains(x)).Select(x=>x.id).toList();//Assume Count = 300
  allIds.RemoveAll(x=>migratedIds.contains(x));
  report.MigratedIds = new IdsInfo(migratedIds);
  return report;
}

What I was expecting =>

Report.AllIds.Count = 1000
Report.AllIds.Ids= {1000}//List
Report.MigratedIds.Count = 300
Report.AllIds.Ids= {300}//List

What actually happened =>

Report.AllIds.Count = 1000
Report.AllIds.Ids= {300}//List
Report.MigratedIds.Count = 300
Report.AllIds.Ids= {300}//List

I understand that Pass By Reference is what is causing this behavior but I would like to understand the reason behind this. My questions are as follows:

  1. Since I am creating a new instance of the Class IdsInfo shouldn't it avoid Pass By Reference?
  2. If Ans for 1 is no, then why is it that only the Ids List is updated and has 300 and the Count remains as is?
  3. How to avoid this? I understand that I can use FindAll instead of RemoveAll and assign the values to a new variable, but what if I want to reuse the same variable as shown.

Upvotes: 2

Views: 50

Answers (2)

Guru Stron
Guru Stron

Reputation: 141835

List<T> is a reference type, so passing allIds into IdsInfo constructor will make report.AllIds and allIds point to the same one instance of the list, resulting in all modifying operations (like RemoveAll) being observed in both places.

If you want to modify allIds later without affecting the instance inside the report just create a copy of the list with ToList (note that it shallow copies the contents of the list, which is fine for string's but for other reference types can lead to similar behavior for elements of the list):

report.AllIds = new IdsInfo(allIds.ToList());

Upvotes: 2

MakePeaceGreatAgain
MakePeaceGreatAgain

Reputation: 37000

Only because you create a new object of your type doesn´t mean you´re also creating a new list. In this line you just add a new reference to the same list.

report.AllIds = new IdsInfo(allIds);

So both report.AllIds and allIds will now reference the exact same list. Doing something on that list - e.g. by calling RemoveAll- is then reflected in all references to that list. You can read more about reference-types in the docs.

So what you need is to copy the list:

report.AllIds = new IdsInfo(allIds.ToList());

ALternativly you can also create the copy within the constructor of IdsInfo, as Guru mentioned in his answer.

ToList will really create a new list from the input-one, even if the input already is a list itself.

Upvotes: 1

Related Questions