【问题标题】:C# List<T> internalsC# List<T> 内部结构
【发布时间】:2011-03-10 00:40:27
【问题描述】:

将对象添加到 List 等集合时究竟会发生什么?

List<Person> people = new List<Person>();
Person dan = new Person() { Name="daniel", Age=10 };
people.Add(dan);
dan = new Person() { Name = "hw", Age = 44 };

当我执行上面的代码时,List&lt;Person&gt; people不会受到最后一行的影响。 所以我假设列表复制添加对象的references,所以当更改引用时,列表不会受到影响。我说的对吗?

【问题讨论】:

  • 在哪里添加对象到列表?你能修复你的代码吗?
  • 哎呀!忘记添加对象。谢天谢地,stackoverflow 的聪明头脑明白我想要表达的意思。

标签: c# generics list


【解决方案1】:

好吧,您显示的代码实际上并没有在列表中添加任何内容。我假设你的意思是:

people.Add(dan);

在哪里?

但是是的,List&lt;T&gt; 包含对对象的引用。它不知道引用的来源。变量与其值之间的区别值得清楚。当你打电话时:

people.Add(dan);

在这种情况下将参数表达式 (dan) 的值复制为 List&lt;T&gt;.Add 中参数的初始值。该值只是一个引用 - 它与 dan 变量没有关联,只是它恰好是调用 List&lt;T&gt;.Add 时的变量值。

和下面的情况完全一样:

Person p1 = new Person { Name = "Jon" };
p2 = p1;
p1 = new Person { Name = "Dan" };

这里,p2 的值仍然是对名为“Jon”的人的引用——第二行只是将p1 的值复制到p2,而不是将两个变量关联在一起。一旦理解了这一点,就可以将相同的逻辑应用于方法参数。

【讨论】:

  • 在您给出的 p1 和 p2 示例中,Person "Jon" 继续存在的唯一原因是因为 p2 = p1 分配,对吗?如果该分配不存在,那么“Dan”的创建将通过 GC 消除“Jon”的实例,如果我是正确的?谢谢。
  • @Sabuncu:嗯,实际上并不是“Dan”的创建“消灭”了另一个对象——而是没有更多的引用来保持另一个对象的存活。 (请注意,何时另一个对象会被收集,没什么好说的。)值得将另一个对象的创建与 GC 方面分开。例如,这并不像一个替换另一个。
  • 谢谢!这是一个简单的问题,但我知道你的答案将是一次学习经历。
【解决方案2】:

那个代码,List&lt;Person&gt; people 也不应该受到第二行的影响。

无论如何,如果您使用 .Add()List&lt;Person&gt; 会获得参考,因此如果您稍后修改该人,列表中的人也会被“修改”(它是同一个人),但如果您将该人分配给其他人您没有影响参考,您正在将符号分配给新参考。

例如:

List<Person> people = new List<Person>(); 
Person dan = new Person() { Name="daniel", Age=10 }; 
people.Add(dan);

/* DanData = { Name="daniel", Age=10 }; 
 * `people[0]` maps to "DanData"
 * `dan` maps to "DanData" */

dan.Name = "daniel the first";
string dansNameInList = people[0].Name; /* equals "daniel the first" */

/* DanData = { Name="daniel the first", Age=10 }; 
 * `people[0]` maps to "DanData"
 * `dan` maps to "DanData" */

people[0].Name = "daniel again";
string dansName = dan.Name /* equals "daniel again" */

/* DanData = { Name="daniel again", Age=10 }; 
 * `people[0]` maps to "DanData"
 * `dan` maps to "DanData" */

dan = new Person() { Name = "hw", Age = 44 }; 
string dansNameInListAfterChange = people[0].Name /* equals "daniel again" */
string dansNameAfterChange = dan.Name /* equals "hw" */

/* DanData = { Name="daniel again", Age=10 }; 
 * NewData = { Name = "hw", Age = 44 }; 
 * `people[0]` maps to "DanData"
 * `dan` maps to "NewData" */

【讨论】:

    【解决方案3】:

    要回答最初的问题,假设您打算在调用第三行之前将该人添加到列表中:

    是的,列表只存储对对象的引用。在 C# 中,ALL 对象变量是引用。

    所以当你第二次调用new Person() 时,你的变量dan 持有的引用被更新为指向新的实例。如果第一个实例没有指向它的引用,它将在下一轮被垃圾收集。如果您已将第一个实例添加到列表中,该列表仍将保留它对第一个实例的引用。

    【讨论】:

      【解决方案4】:

      在 .NET 编程中,很好地理解引用类型和值类型之间的区别很重要。这篇文章提供了一个很好的概述: http://www.albahari.com/valuevsreftypes.aspx

      在本例中,您创建了两个 Person 对象(Daniel,10 岁,hw,44 岁)。是引用这两个对象的变量造成了混淆。


      Person dan = new Person() { Name="daniel", Age=10 };
      

      这里,为一个局部变量 dan 分配了一个对新创建的(Daniel,10 岁)对象的引用。


      people.Add(dan);
      

      这里,属性 people[0] (间接通过 List.Add 方法)分配了对现有(Daniel,10 岁)对象的引用。


      dan = new Person() { Name = "hw", Age = 44 };
      

      在这里,局部变量 dan 被分配了对新创建的 (hw, age 44) 对象的引用。但是属性 people[0] 仍然持有对现有(Daniel,10 岁)对象的引用。

      【讨论】:

        【解决方案5】:

        在代码末尾添加

        people[0] = dan;
        

        将指向您的新 dan 对象。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-02-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多