2010-05-05 19 views
142

比方說,我有一個類ToList() - 它是否創建一個新列表?

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:如果看起來很明顯,我很抱歉。但我現在沒有與我編譯器...

回答

171

是的,ToList會創建一個新列表,但是因爲在這種情況下MyObject是一個引用類型,那麼新列表將包含對與原始列表相同的對象的引用。

更新新列表中引用的對象的SimpleInt屬性也會影響原始列表中的等效對象。

(如果MyObject被宣佈爲struct而非class,則新的列表將包含元素的副本在最初的名單,並在新的列表更新元素的屬性將不會影響相當於元素在原始列表中。)

+1

也要注意,使用結構的一個'List',像一個分配'鏈表類[0] = .SimpleInt 5'將不被允許(C#編譯時錯誤)。這是因爲列表索引器的「get」訪問器的返回值不是變量(它是結構值的返回副本),因此不允許使用賦值表達式設置其成員'.SimpleInt'(它將會發生變化不保存的副本)。那麼,誰使用可變結構呢? – 2016-07-06 16:31:29

+1

@Jeppe Stig Nielson:是的。同樣,編譯器也會阻止你像'foreach(var s in listOfStructs){s.SimpleInt = 42; }'。該_really_討厭的小問題,就是當你試圖像'listOfStructs.ForEach(S => s.SimpleInt = 42)':編譯器允許它和代碼運行沒有異常,但在列表中的結構將保持不變! – LukeH 2016-07-06 17:03:05

29

ToList將始終創建一個新列表,它不會反映對集合的任何後續更改。

但是,它會反映對象本身的變化(除非它們是可變結構)。

換句話說,如果您將原始列表中的對象替換爲其他對象,則ToList仍將包含第一個對象。
但是,如果您修改原始列表中的某個對象,則ToList仍將包含相同(已修改)的對象。

6

將創建一個新列表,但其中的項目是對原始項目的引用(就像在原始列表中一樣)。對列表本身的更改是獨立的,但對於這兩個列表中的項目都會發現更改。

10

是的,它會創建一個新列表。這是設計。

該列表將包含與原始可枚舉序列相同的結果,但會實現爲持久(內存中)集合。這使您可以多次使用結果,而不會產生重新計算序列的成本。

LINQ序列的美妙之處在於它們是可組合的。通常,您獲得的IEnumerable<T>是組合多個過濾,排序和/或投影操作的結果。擴展方法如ToList()ToArray()允許您將計算出的序列轉換爲標準集合。

2

ToList將創建一個全新的列表。

如果列表中的項目是值類型,它們將被直接更新,如果它們是引用類型,則任何更改都會反射回引用的對象。

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

這將更新原始對象。新列表將包含對其中包含的對象的引用,就像原始列表一樣。您可以更改元素,並且更新將反映在另一箇中。

現在,如果更新列表(添加或刪除一個項目),將不會反映在另一個列表中。

4

我認爲這相當於詢問ToList是否進行深層或淺層複製。由於ToList沒有辦法克隆MyObject的,它必須做一個淺拷貝,因此所創建的列表中包含與原相同的標記,所以代碼返回5

55

從Reflector'd來源:

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

所以是的,你的原始列表將不會被更新(即增加或刪除),但是被引用的對象會。

1

我沒有看到ToList()始終保證返回一個新列表的文檔中的任何地方。如果一個IEnumerable是一個List,檢查這個並且簡單地返回相同的List可能更有效率。

擔心的是,有時您可能希望確定返回的列表是!=原始列表。因爲微軟沒有記錄ToList會返回一個新的列表,我們不能確定(除非有人發現該文檔)。即使它現在有效,它也可能在未來發生變化。

新List(IEnumerable enumerablestuff)保證返回一個新List。我會用這個來代替。

+2

它說,在文檔中的位置:「[該ToList (IEnumerable的)方法強制立即查詢評估,並返回包含查詢結果列表您可以將這個方法來查詢,以獲得的緩存副本。查詢結果。](http://msdn.microsoft.com/en-us/library/bb342261.aspx)「第二句重申,評估查詢後對原始列表所做的任何更改都不會影響返回的列表。 – 2013-08-26 04:42:24

+1

@RaymondChen對於IEnumerables而言,這是正確的,但不適用於列表。儘管'ToList'在List列表上調用時似乎創建了一個新的列表對象引用,但是當BenB說MS並沒有保證這一點時,他是正確的。 – Teejay 2016-04-07 08:58:35

+0

@RaymondChen作爲每CHRISS源,'的IEnumerable <>。ToList()'實際上作爲新'列表<>(源)執行',並且沒有具體的倍率爲'列表<>',所以'列表<>。ToList ()確實返回一個新的列表對象引用。但再次,根據MS文檔,將來不能保證不會改變,儘管它可能會破壞大部分代碼。 – Teejay 2016-04-07 09:14:47

2

如果源對象是真正的IEnumerable(即,不僅僅是一個集合打包爲可枚舉的集合),ToList()可能不會返回與原始IEnumerable中相同的對象引用。它將返回一個新的對象列表,但這些對象可能與IEnumerable在枚舉時產生的對象不同或相等。

7

接受的答案根據他們的示例正確地解決了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; 
} 

雖然上述代碼可能會出現人爲的,這種行爲可以顯示爲在其他方案中一個微妙的錯誤。請參閱my other example以瞭解導致任務重複產生的情況。

4

只是偶然發現了這個舊帖子,想到要加我兩毛錢。一般來說,如果我有疑問,我很快在任何對象上使用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(); 
    } 

,並回答我的機器上 -

  • OBJ文件:45653674個
  • OBJ文件[0]:41149443
  • 對象:45653674個
  • 對象[0]:41149443
  • objectList:39785641
  • objectList [0]:41149443
  • whatInt:5

所以基本上該列表承載對象留在上面的代碼中是相同的。希望這種方法有幫助。

相關問題