ToList()-是否创建一个新列表?


181

假设我有一堂课

public class MyObject
{
   public int SimpleInt{get;set;}
}

我有一个List<MyObject>ToList()然后将其更改为一个,然后SimpleInt将更改传播回原始列表。换句话说,以下方法的输出是什么?

public void RunChangeList()
{
  var objs = new List<MyObject>(){new MyObject(){SimpleInt=0}};
  var whatInt = ChangeToList(objs );
}
public int ChangeToList(List<MyObject> objects)
{
  var objectList = objects.ToList();
  objectList[0].SimpleInt=5;
  return objects[0].SimpleInt;

}

为什么?

P / S:很抱歉,如果找不到答案。但是我现在没有编译器了...


措辞的一种方法是.ToList()制作浅表副本。引用被复制,但是新引用仍然指向与原始引用相同的实例。当你想到它,ToList不能创造任何new MyObject()时候MyObject是一个class类型。
杰普·斯蒂格·尼尔森

Answers:


224

是的,ToList将创建一个新列表,但是由于在这种情况下MyObject是引用类型,因此新列表将包含对与原始列表相同的对象的引用。

更新SimpleInt新列表中引用的对象的属性也会影响原始列表中的等效对象。

(如果MyObject被宣布为一个struct,而不是一个class那么新的列表将包含在原来的列表中元素的副本,并在新的列表更新元素的属性将不会影响到原来的列表中的等效元素。)


2
还要注意,使用aList结构,objectList[0].SimpleInt=5将不允许进行类似的赋值(C#编译时错误)。这是因为列表索引器的get访问器的返回值不是变量(它是结构值的返回副本),因此不允许使用赋值表达式设置其成员.SimpleInt(这会使未保留的副本发生变异) 。那么,谁仍然使用可变结构?
Jeppe Stig Nielsen

3
@Jeppe Stig Nielson:是的。而且,类似地,编译器也会阻止您执行诸如之类的工作foreach (var s in listOfStructs) { s.SimpleInt = 42; }。在讨厌的小问题,就是当你尝试类似listOfStructs.ForEach(s => s.SimpleInt = 42):编译器允许并没有异常的代码运行,但在列表中的结构将保持不变!
LukeH '16

2
另一个注意事项:清除原始List不会从内存中删除其元素(新List中的引用仍将指向调用ToList时引用的值)。
ryanwebjackson

64

从Reflector的来源:

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    return new List<TSource>(source);
}

所以是的,您的原始列表不会被更新(即添加或删除),但是引用的对象将被更新。


33

ToList 将始终创建一个新列表,该列表不会反映对该集合的任何后续更改。

但是,它将反映对象本身的更改(除非它们是可变结构)。

换句话说,如果将原始列表中的对象替换为其他对象,ToList则仍将包含第一个对象。
但是,如果您修改了原始列表中的对象之一,ToList则仍将包含相同(已修改)的对象。


12

接受的答案根据其示例正确地解决了OP的问题。但是,它仅在ToList应用于具体集合时才适用。当源序列的元素尚未实例化时(由于延迟执行),它不成立。如果是后者,则每次调用ToList(或枚举顺序)可能会得到一组新的项目。

这是OP的代码的改编,以演示此行为:

public static void RunChangeList()
{
    var objs = Enumerable.Range(0, 10).Select(_ => new MyObject() { SimpleInt = 0 });
    var whatInt = ChangeToList(objs);   // whatInt gets 0
}

public static int ChangeToList(IEnumerable<MyObject> objects)
{
    var objectList = objects.ToList();
    objectList.First().SimpleInt = 5;
    return objects.First().SimpleInt;
}

尽管上面的代码可能是人为设计的,但在其他情况下,此行为可能会显示为细微的错误。请参阅我的另一个示例,以了解导致任务重复产生的情况。


11

是的,它将创建一个新列表。这是设计使然。

该列表将包含与原始可枚举序列相同的结果,但具体化为持久(内存中)集合。这使您可以多次使用结果,而无需花费重新计算序列的费用。

LINQ序列的优点在于它们是可组合的。通常,IEnumerable<T>您得到的是组合多个过滤,排序和/或投影操作的结果。扩展方法喜欢ToList()和,ToArray()您可以将计算出的序列转换为标准集合。


7

创建了一个新列表,但是其中的项目是对原始项目的引用(就像在原始列表中一样)。列表本身的更改是独立的,但是对项目的更改将在两个列表中都找到。


6

只是偶然发现了这个旧职位,并想加我两美分。通常,如果我有疑问,我会在任何对象上快速使用GetHashCode()方法来检查身份。所以对于以上-

    public class MyObject
{
    public int SimpleInt { get; set; }
}


class Program
{

    public static void RunChangeList()
    {
        var objs = new List<MyObject>() { new MyObject() { SimpleInt = 0 } };
        Console.WriteLine("objs: {0}", objs.GetHashCode());
        Console.WriteLine("objs[0]: {0}", objs[0].GetHashCode());
        var whatInt = ChangeToList(objs);
        Console.WriteLine("whatInt: {0}", whatInt.GetHashCode());
    }

    public static int ChangeToList(List<MyObject> objects)
    {
        Console.WriteLine("objects: {0}", objects.GetHashCode());
        Console.WriteLine("objects[0]: {0}", objects[0].GetHashCode());
        var objectList = objects.ToList();
        Console.WriteLine("objectList: {0}", objectList.GetHashCode());
        Console.WriteLine("objectList[0]: {0}", objectList[0].GetHashCode());
        objectList[0].SimpleInt = 5;
        return objects[0].SimpleInt;

    }

    private static void Main(string[] args)
    {
        RunChangeList();
        Console.ReadLine();
    }

在我的机器上回答-

  • 对象:45653674
  • objs [0]:41149443
  • 对象:45653674
  • 对象[0]:41149443
  • objectList:39785641
  • objectList [0]:41149443
  • whatInt:5

因此,列表所携带的对象本质上在上述代码中保持不变。希望该方法有所帮助。


4

我认为这等同于询问ToList是进行深层复制还是浅层复制。由于ToList无法克隆MyObject,因此它必须进行浅表复制,因此创建的列表包含与原始列表相同的引用,因此代码返回5。


2

ToList 将创建一个全新的列表。

如果列表中的项目是值类型,则将直接更新它们;如果它们是引用类型,则所有更改都将反映在引用的对象中。


2

在源对象是真正的IEnumerable的情况下(即,不仅仅是打包为枚举的集合),ToList()可能不会返回与原始IEnumerable相同的对象引用。它将返回一个新的对象列表,但是当再次枚举IEnumerable时,这些对象可能与IEnumerable所产生的对象不同,甚至相等。


1
 var objectList = objects.ToList();
  objectList[0].SimpleInt=5;

这也将更新原始对象。就像原始列表一样,新列表将包含对其中包含的对象的引用。您可以更改任何一个元素,而更新将反映在另一个元素中。

现在,如果您更新一个列表(添加或删除项目),该列表将不会反映在其他列表中。


1

我没有在文档中看到ToList()总是保证返回新列表。如果IEnumerable是一个List,则检查此内容并简单地返回相同的List可能会更有效。

担心的是,有时您可能要绝对确定返回的List是原始List的!=。因为Microsoft并未记录ToList将返回新的List,所以我们不能确定(除非有人找到了该文档)。即使现在可行,将来也可能改变。

保证新的List(IEnumerable enumerablestuff)返回一个新的List。我会用它代替。


2
它在这里的文档中说:“ ToList <TSource>(IEnumerable <TSource>)方法强制执行立即查询评估并返回包含查询结果的List <T>。您可以将此方法附加到查询中以获得查询结果的缓存副本。 “第二句重申,到查询后原来的列表中的任何变化进行评估有返回的列表上没有影响。
Raymond Chen

1
@RaymondChen对于IEnumerables而言是正确的,但对于Lists而言则不是。尽管ToList在调用a时似乎会创建一个新的列表对象引用List,但BenB说对了,这是MS文档无法保证的。
2016年

@RaymondChen根据ChrisS来源,IEnumerable<>.ToList()实际上是按实现的,new List<>(source)并且没有的特定替代List<>,因此List<>.ToList()确实会返回新的列表对象引用。但是再一次,根据MS文档,虽然将来可能会破坏大多数代码,但不能保证将来不会更改。
Teejay
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.