ToList() – 它是否创build一个新的列表?
假设我有一堂课
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
将创build一个新的列表,但是因为在这种情况下, MyObject
是一个引用types,那么新列表将包含与原始列表相同的对象的引用。
更新新列表中引用的对象的SimpleInt
属性也将影响原始列表中的等效对象。
(如果MyObject
被声明为struct
而不是class
那么新列表将包含原始列表中元素的副本,并且更新新列表中元素的属性不会影响原始列表中的等效元素。
来自Reflector'd的来源:
public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source) { if (source == null) { throw Error.ArgumentNull("source"); } return new List<TSource>(source); }
所以是的,你的原始列表将不会更新(即增加或删除),但引用的对象将会。
ToList将始终创build一个新的列表,这将不会反映对集合的任何后续更改。
但是,它会反映对象本身的变化(除非它们是可变结构)。
换句话说,如果用不同的对象replace原始列表中的对象, ToList
将仍然包含第一个对象。
但是,如果您修改原始列表中的某个对象, ToList
仍将包含相同(已修改)的对象。
是的,它创build了一个新的列表。 这是devise。
该列表将包含与原始可枚举序列相同的结果,但将其物化为持久(内存)集合。 这使您可以多次使用结果,而不会产生重新计算序列的成本。
LINQ序列的美妙之处在于它们是可组合的。 通常,您获得的IEnumerable<T>
是组合多个过滤,sorting和/或投影操作的结果。 像ToList()
和ToArray()
这样的扩展方法可以将计算出的序列转换为标准的集合。
创build一个新的列表,但其中的项目是对原始项目的引用(就像在原始列表中一样)。 列表本身的变化是独立的,但是对于这两个列表中的项目都会发现变化。
接受的答案根据他们的例子正确地解决了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; }
虽然上面的代码可能会出现,但这种行为在其他情况下可能会显示为一个微妙的错误。 看到我的另一个例子 ,它导致任务重复产生的情况。
我认为这相当于询问ToList是做一个深层还是浅层的拷贝。 由于ToList无法克隆MyObject,因此它必须执行浅拷贝,所以创build的列表包含与原始列表相同的引用,所以代码返回5。
只是偶然发现这个旧post,想到要加两分钱。 一般来说,如果我有疑问,我很快在任何对象上使用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
所以基本上这个列表的对象在上面的代码中保持不变。 希望这个方法有帮助。
ToList
将创build一个全新的列表。
如果列表中的项目是值types,则它们将被直接更新,如果它们是引用types,则任何更改都会reflection回引用的对象中。
在源对象是一个真正的IEnumerable的情况下(即不只是一个集合打包为一个可枚举),ToList()可能不会返回与原始IEnumerable中相同的对象引用。 它将返回一个新的对象列表,但是这些对象可能与IEnumerable在枚举时再次列举的对象不同
var objectList = objects.ToList(); objectList[0].SimpleInt=5;
这将更新原始对象。 新列表将包含对其中包含的对象的引用,就像原始列表一样。 您可以更改元素,更新将反映在另一个。
现在,如果您更新列表(添加或删除项目),将不会反映在另一个列表中。
我没有看到ToList()总是保证返回一个新列表的文档中的任何地方。 如果一个IEnumerable是一个List,检查这个并且简单的返回相同的List可能会更有效率。
担心的是,有时候您可能希望确定返回的列表是!=原始列表。 因为微软没有loggingToList会返回一个新的List,所以我们不能确定(除非有人发现这个文档)。 即使现在有效,它也可能在未来发生变化。
新的List(IEnumerable enumerablestuff)保证返回一个新的List。 我会用这个来代替。