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

Gra*_*ton 165 c# linq

假设我有一堂课

public class MyObject
{
   public int SimpleInt{get;set;}
}
Run Code Online (Sandbox Code Playgroud)

我有一个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;

}
Run Code Online (Sandbox Code Playgroud)

为什么?

P/S:如果发现它似乎很明显,我很抱歉.但我现在没有编译器...

Luk*_*keH 203

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

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

(如果MyObject声明为a struct而不是a,class则新列表将包含原始列表中元素的副本,并且更新新列表中元素的属性不会影响原始列表中的等效元素.)

  • 还要注意,对于结构的`List`,不允许像`objectList[0].SimpleInt=5`这样的赋值(C# 编译时错误)。这是因为列表索引器的“get”访问器的返回值不是变量(它是结构值的返回副本),因此不允许使用赋值表达式设置其成员“.SimpleInt”(它会发生变异)未保留的副本)。那么,谁会使用可变结构呢? (2认同)
  • @Jeppe Stig Nielson:是的。而且,类似地,编译器还将阻止您执行诸如`foreach(listOfStructs中的var s){s.SimpleInt = 42; }`。_really_令人讨厌的陷阱是当您尝试类似`listOfStructs.ForEach(s =&gt; s.SimpleInt = 42)`之类的东西时:编译器允许它并且代码运行无异常,但是列表中的结构将保持不变! (2认同)
  • 另请注意:清除原始 List 不会从内存中删除其元素(新 List 中的引用仍将指向调用 ToList 时引用的值)。 (2认同)

Chr*_*s S 58

来自Reflector'd来源:

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

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


SLa*_*aks 31

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

但是,它会反映对象本身的变化(除非它们是可变的结构).

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


LBu*_*kin 11

是的,它会创建一个新列表.这是设计的.

该列表将包含与原始可枚举序列相同的结果,但具体化为持久性(内存中)集合.这允许您多次使用结果,而不会产生重新计算序列的成本.

LINQ序列的美妙之处在于它们是可组合的.通常,IEnumerable<T>您获得的是结合多个过滤,排序和/或投影操作的结果.扩展方法一样ToList(),并ToArray()让您的计算机序列转换成标准的集合.


Dou*_*las 11

接受的答案根据他的例子正确地解决了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;
}
Run Code Online (Sandbox Code Playgroud)

虽然上面的代码可能看似人为,但这种行为在其他场景中可能会出现一个微妙的错误.请参阅我的另一个示例,了解导致任务重复生成的情况.


Pre*_*gha 6

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


vib*_*bhu 5

只是偶然发现这个旧帖子,想到加上我的两分钱.通常,如果我有疑问,我会在任何对象上快速使用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();
    }
Run Code Online (Sandbox Code Playgroud)

并在我的机器上回答 -

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

基本上列表中携带的对象在上面的代码中保持不变.希望这种方法有所帮助