ToList () - yeni bir liste oluşturuyor mu?


176

Diyelim ki bir dersim var

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

Ve bir tane var List<MyObject>ve benToList() ve sonra birini değiştirdim SimpleInt, değişikliğim orijinal listeye geri yayılacak. Başka bir deyişle, aşağıdaki yöntemin çıktısı ne olur?

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;

}

Neden?

P / S: Öğrenmesi açık görünüyorsa özür dilerim. Ama şimdi benimle derleyici yok ...


Bunu phrasing bir yolu, yani .ToList()bir kılan sığ kopya. Referanslar kopyalanır, ancak yeni referanslar hala orijinal referansların işaret ettiği aynı örnekleri gösterir. Bunu düşündüğünüzde , bir tür olduğunda ToListhiçbir new MyObject()zaman oluşturamazsınız . MyObjectclass
Jeppe Stig Nielsen

Yanıtlar:


217

Evet, ToListyeni bir liste oluşturacak, ancak bu durumdaMyObject bir başvuru türü , yeni liste orijinal listeyle aynı nesnelere başvurular içerir.

Güncelleniyor SimpleIntYeni listede başvurulan bir nesnenin özelliğinin orijinal listedeki eşdeğer nesneyi de etkiler.

(Eğer MyObject bir olarak ilan edilmiştir structyerine bir daha classözgün listedeki elemanların kopyalarını içerecektir sonra yeni liste ve yeni listenin öğesi bir özelliğini güncellenmesi olur değil orijinal listedeki eşdeğer elemanını etkiler.)


1
Ayrıca bir Listyapı ile, atamaya objectList[0].SimpleInt=5izin verilmeyeceğini unutmayın (C # derleme zamanı hatası). Bunun nedeni, liste dizinleyicisinin geterişimcisinin dönüş değerinin bir değişken olmaması (bir yapı değerinin döndürülmüş bir kopyasıdır) ve bu nedenle üyesini atama ifadesiyle ayarlamasına.SimpleInt izin verilmez (tutulmayan bir kopyayı değiştirir) . Peki, kim değişebilir yapıları kullanıyor?
Jeppe Stig Nielsen

2
@Jeppe Stig Nielson: Evet. Ve benzer şekilde, derleyici de sizin gibi şeyler yapmayı durduracaktır foreach (var s in listOfStructs) { s.SimpleInt = 42; }. Gerçekten böyle bir şey çalıştığınızda pis gotcha olduğunu listOfStructs.ForEach(s => s.SimpleInt = 42)derleyici onu ve istisnasız kod çalışır sağlar, ancak listedeki yapılar değişmeden kalacak:!
LukeH

62

Reflektör kaynağından:

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

Bu nedenle evet, orijinal listeniz güncellenmez (yani eklemeler veya kaldırmalar), ancak başvurulan nesneler olacaktır.


32

ToList her zaman koleksiyonda daha sonra yapılacak değişiklikleri yansıtmayacak yeni bir liste oluşturur.

Bununla birlikte, nesnelerin kendisindeki değişiklikleri yansıtacaktır (Değişebilir yapılar olmadıkça).

Başka bir deyişle, orijinal listedeki bir nesneyi farklı bir nesneyle değiştirirseniz, nesne ToListyine de ilk nesneyi içerir.
Ancak, orijinal listedeki nesnelerden birini değiştirirseniz ToList, yine de aynı (değiştirilmiş) nesneyi içerir.


12

Kabul edilen cevap, OP'nin sorusunu örneğine göre doğru bir şekilde ele almaktadır. Ancak, yalnızca ToListsomut bir koleksiyona uygulandığında geçerlidir ; kaynak dizinin elemanları henüz somutlaştırılmadığında (ertelenmiş yürütme nedeniyle) tutmaz. İkincisi durumunda, her aradığınızda yeni bir öğe kümesi alabilirsinizToList (veya diziyi numaralandırdığınızda) .

Bu davranışı göstermek için OP kodunun uyarlaması:

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;
}

Yukarıdaki kod yapılandırılmış gibi görünse de, bu davranış diğer senaryolarda küçük bir hata olarak görünebilir. Bkz benim diğer örnek de defalarca kökenli almak için görevleri neden olan bir durum için.


11

Evet, yeni bir liste oluşturur. Bu tasarım gereğidir.

Liste orijinal numaralandırılabilir diziyle aynı sonuçları içerecektir, ancak kalıcı (bellek içi) bir koleksiyon halinde gerçekleştirilecektir. Bu, diziyi yeniden hesaplama maliyetine maruz kalmadan sonuçları birden çok kez tüketmenizi sağlar.

LINQ dizilerinin güzelliği, birleştirilebilir olmalarıdır. Genellikle, IEnumerable<T>birden fazla filtreleme, sipariş ve / veya projeksiyon işlemini birleştirmenin sonucudur. Hesaplanan diziyi standart bir koleksiyona dönüştürmenize izin veren ToList()ve genişletme yöntemleri ToArray().


6

Yeni bir liste oluşturulur, ancak içindeki öğeler orijinal öğelere referanstır (tıpkı orijinal listedeki gibi). Listenin kendisindeki değişiklikler bağımsızdır, ancak öğelerdeki değişiklik her iki listede de bulunur.


6

Sadece bu eski yazı üzerine tökezlemek ve benim iki sent eklemeyi düşündüm. Genellikle, şüphem varsa, kimliklerini kontrol etmek için herhangi bir nesne üzerinde hızlı bir şekilde GetHashCode () yöntemini kullanın. Yani yukarıda -

    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();
    }

Ve makineme cevap ver -

  • objs: 45653674
  • objs [0]: 41149443
  • nesneler: 45653674
  • objeler [0]: 41149443
  • objectList: 39785641
  • objectList [0]: 41149443
  • ne: 5

Bu nedenle, esasen liste taşıyan nesne yukarıdaki kodda aynı kalır. Umarım yaklaşım yardımcı olur.


4

Bunun ToList'in derin veya sığ bir kopya yapıp yapmadığını sormaya eşdeğer olduğunu düşünüyorum. ToList, MyObject'i klonlamanın hiçbir yolu olmadığından, sığ bir kopya yapmalıdır, bu nedenle oluşturulan liste orijinaliyle aynı referansları içerir, böylece kod 5'i döndürür.


2

ToList yepyeni bir liste oluşturacak.

Listedeki öğeler değer türüyse, bunlar doğrudan güncelleştirilir, başvuru türleri ise, yapılan değişiklikler başvurulan nesnelere geri yansıtılır.


2

Kaynak nesnenin gerçek bir IEnumerable (yani yalnızca numaralandırılabilir olarak paketlenmiş bir koleksiyon değil) olması durumunda, ToList () orijinal IEnumerable'daki ile aynı nesne referanslarını döndürmeyebilir. Yeni bir Nesne Listesi döndürür, ancak bu nesneler, yeniden numaralandırıldığında IEnumerable tarafından verilen nesnelerle aynı veya hatta Eşit olmayabilir


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

Bu, orijinal nesneyi de güncelleyecektir. Yeni liste, tıpkı orijinal liste gibi, içinde yer alan nesnelere referanslar içerecektir. Öğeleri de değiştirebilirsiniz; güncelleme diğerine de yansıtılır.

Şimdi diğer listeye yansıtılmayacak bir listeyi (bir öğe ekleyerek veya silerek) güncellerseniz.


1

Belgelerde ToList'in () her zaman yeni bir liste döndürmesinin garanti edildiğini görmüyorum. IEnumerable bir Liste ise, bunu kontrol etmek ve aynı Listeyi döndürmek daha verimli olabilir.

Endişe, bazen iade edilen listenin orijinal listeye! = Olduğundan kesinlikle emin olmak isteyebilirsiniz. Microsoft, ToList'in yeni bir Liste döndüreceğini belgelemediğinden emin olamayız (birisi bu belgeleri bulamazsa). Şimdi çalışıyor olsa bile gelecekte de değişebilir.

yeni Liste'nin (IEnumerable enumerablestuff) yeni bir Liste döndürmesi garanti edilir. Bunun yerine bunu kullanırdım.



1
@RaymondChen Bu IEnumerables için geçerlidir, ancak Listeler için geçerli değildir. Her ne kadar ToListbir çağrıda zaman yeni bir liste nesnesi referans oluşturmak gibi görünüyor Listo bu MS belgelerine tarafından garanti olmadığını söylerken, BenB haklı.
Teejay

@RaymondChen ChrisS kaynağına göre IEnumerable<>.ToList(), aslında olarak uygulanır new List<>(source)ve bunun için belirli bir geçersiz kılma yoktur List<>, bu yüzden List<>.ToList()gerçekten yeni bir liste nesnesi başvurusu döndürür. Ancak bir kez daha, MS belgelerine göre, gelecekte kodun çoğunu kıracak olmasına rağmen, gelecekte değişmemesi için bir garanti yoktur.
Teejay
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.