C#:将 属性 初始赋值给包含 List<string> 的变量会在稍后对同一变量使用 RemoveAll 时得到更新

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

我们正在执行迁移,我们应该记录相同的最终报告。为了达到同样的目的,我有一个 class 它有多个属性,其中包含 ID 列表(字符串),例如迁移中涉及的所有 ID、所有已迁移的 ID 等。 在做同样的事情时,我遇到的是,当我对包含所有 ID 列表的变量执行 RemoveAll 时,我之前的分配正在更新,为了更好地理解,PFB 示例:

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

我所期待的=>

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

实际发生了什么=>

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

我知道通过引用传递是导致此行为的原因,但我想了解其背后的原因。我的问题如下:

  1. 因为我正在创建 Class IdsInfo 的新实例,它不应该避免通过引用传递吗?
  2. 如果 1 的 Ans 为否,那为什么只有 IDs List 被更新并且有 300 而 Count 保持不变?
  3. 如何避免这种情况?我知道我可以使用 FindAll 而不是 RemoveAll 并将值分配给一个新变量,但是如果我想重复使用所示的相同变量怎么办。

List<T>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) 在两个地方都被观察到。

如果你想稍后修改 allIds 而不影响 report 中的实例,只需使用 ToList 创建列表的副本(注意它浅拷贝列表,这对 string 很好,但对于其他引用类型可能会导致列表元素的类似行为):

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

仅仅因为您创建了一个您的类型的新对象并不意味着您也在创建一个新的列表。在这一行中,您只需将新的 reference 添加到同一列表。

report.AllIds = new IdsInfo(allIds);

因此 report.AllIdsallIds 现在将引用完全相同的列表。在该列表上做某事 - 例如通过调用 RemoveAll- 然后反映在对该列表的所有引用中。你可以阅读 more about reference-types in the docs.

所以你需要的是复制列表:

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

或者,您也可以在 IdsInfo 的构造函数中创建副本,正如 Guru 在他的回答中提到的那样。

ToList 真正地 从输入一创建一个新列表,即使输入本身已经是一个列表。