ToList () - สร้างรายการใหม่หรือไม่


176

สมมติว่าฉันมีชั้นเรียน

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ประเภท
Jeppe Stig Nielsen

คำตอบ:


217

ใช่ToListจะสร้างรายการใหม่ แต่เนื่องจากในกรณีMyObjectนี้เป็นประเภทการอ้างอิงรายการใหม่จะมีการอ้างอิงไปยังวัตถุเดียวกันกับรายการเดิม

การอัปเดตSimpleIntคุณสมบัติของวัตถุที่อ้างอิงในรายการใหม่จะมีผลกับวัตถุที่เทียบเท่าในรายการต้นฉบับ

(ถ้าMyObjectถูกประกาศให้เป็นstructมากกว่าclassรายการใหม่จะมีสำเนาขององค์ประกอบในรายการต้นฉบับและการปรับปรุงคุณสมบัติขององค์ประกอบในรายการใหม่จะไม่ส่งผลกระทบต่อองค์ประกอบที่เทียบเท่าในรายการเดิม)


1
นอกจากนี้โปรดทราบว่าด้วยโครงสร้างListของการมอบหมายเช่นobjectList[0].SimpleInt=5จะไม่ได้รับอนุญาต (ข้อผิดพลาดเวลารวบรวม C #) นั่นเป็นเพราะค่าส่งคืนของgetaccessor ของตัวทำดัชนีของรายการไม่ใช่ตัวแปร (เป็นสำเนาที่ส่งคืนของค่า struct) ดังนั้นจึงไม่อนุญาตให้ตั้งค่าสมาชิก.SimpleIntด้วยการแสดงออกของการมอบหมาย (มันจะกลายพันธุ์สำเนาที่ไม่ได้เก็บไว้) . ใครใช้ struct ที่ไม่แน่นอน
Jeppe Stig Nielsen

2
@Jeppe Stig Nielson: ใช่ foreach (var s in listOfStructs) { s.SimpleInt = 42; }และในทำนองเดียวกันคอมไพเลอร์จะหยุดคุณทำสิ่งที่ชอบ จริงๆ gotcha ที่น่ารังเกียจคือเมื่อคุณพยายามที่สิ่งที่ต้องการlistOfStructs.ForEach(s => s.SimpleInt = 42): คอมไพเลอร์จะช่วยให้มันและรหัสที่ทำงานโดยไม่มีข้อยกเว้น แต่ structs ในรายการจะอยู่ไม่เปลี่ยนแปลง!
ลุ

62

จากแหล่งสะท้อนแสง:

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

ใช่แล้วรายการดั้งเดิมของคุณจะไม่ได้รับการอัปเดต (เช่นการเพิ่มหรือลบ) แต่วัตถุที่อ้างอิงจะ


32

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

ในขณะที่รหัสข้างต้นอาจปรากฏ contrived พฤติกรรมนี้สามารถปรากฏเป็นข้อบกพร่องเล็กน้อยในสถานการณ์อื่น ๆ ดูตัวอย่างอื่น ๆ ของฉันสำหรับสถานการณ์ที่ทำให้งานเกิดซ้ำ ๆ


11

ใช่มันสร้างรายการใหม่ นี่คือโดยการออกแบบ

รายการจะมีผลลัพธ์เช่นเดียวกับลำดับนับต้นฉบับแต่ปรากฏในคอลเลกชันถาวร (ในหน่วยความจำ) วิธีนี้ช่วยให้คุณสามารถใช้ผลลัพธ์ได้หลายครั้งโดยไม่ต้องเสียค่าใช้จ่ายในการคำนวณลำดับใหม่

ความสวยงามของลำดับ LINQ คือมันสามารถเรียงได้ บ่อยครั้งที่IEnumerable<T>คุณได้รับเป็นผลมาจากการรวมการกรองการสั่งซื้อและ / หรือการฉายภาพหลายรายการเข้าด้วยกัน วิธีการขยายเช่นToList()และToArray()อนุญาตให้คุณแปลงลำดับการคำนวณเป็นคอลเลกชันมาตรฐาน


6

รายการใหม่จะถูกสร้างขึ้น แต่รายการในนั้นจะอ้างอิงถึงรายการเดิม (เช่นเดียวกับในรายการเดิม) การเปลี่ยนแปลงในรายการมีความเป็นอิสระ แต่สำหรับรายการจะพบการเปลี่ยนแปลงในทั้งสองรายการ


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

และตอบคำถามบนเครื่องของฉัน -

  • objs: 45653674
  • objs [0]: 41149443
  • วัตถุ: 45653674
  • วัตถุ [0]: 41149443
  • objectList: 39785641
  • objectList [0]: 41149443
  • อะไรก็ได้: 5

ดังนั้นวัตถุที่รายการนั้นจะยังคงเหมือนเดิมในรหัสข้างต้น หวังว่าวิธีการช่วย


4

ฉันคิดว่านี่เทียบเท่ากับการถามว่า ToList ทำสำเนาแบบลึกหรือตื้น เนื่องจาก ToList ไม่มีวิธีในการโคลน MyObject มันจะต้องทำสำเนาแบบตื้นดังนั้นรายการที่สร้างขึ้นจะมีการอ้างอิงเช่นเดียวกับต้นฉบับดังนั้นรหัสจึงส่งคืน 5


2

ToList จะสร้างรายการใหม่

หากรายการในรายการเป็นประเภทค่ารายการเหล่านั้นจะได้รับการอัปเดตโดยตรงหากเป็นประเภทการอ้างอิงการเปลี่ยนแปลงใด ๆ จะปรากฏในวัตถุที่อ้างอิง


2

ในกรณีที่วัตถุต้นฉบับคือ IEnumerable จริง (เช่นไม่ใช่แค่ชุดข้อมูลที่บรรจุนับได้) ToList () อาจไม่ส่งคืนการอ้างอิงวัตถุเดียวกับใน IEnumerable ดั้งเดิม มันจะส่งคืนรายชื่อของวัตถุใหม่ แต่วัตถุเหล่านั้นอาจไม่เหมือนกันหรือแม้แต่เท่ากับวัตถุที่ให้ผลตอบแทนโดย IEnumerable เมื่อมีการแจกแจงอีกครั้ง


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

นี้จะปรับปรุงวัตถุต้นฉบับเช่นกัน รายการใหม่จะมีการอ้างอิงไปยังวัตถุที่อยู่ภายในเช่นเดียวกับรายการเดิม คุณสามารถเปลี่ยนองค์ประกอบอย่างใดอย่างหนึ่งและการปรับปรุงจะปรากฏในอีก

ตอนนี้หากคุณอัปเดตรายการ (เพิ่มหรือลบรายการ) ที่จะไม่ปรากฏในรายการอื่น


1

ฉันไม่เห็นที่ใดในเอกสารที่รับประกันว่า ToList () จะส่งคืนรายการใหม่เสมอ หาก IEnumerable เป็นรายการมันอาจจะมีประสิทธิภาพมากขึ้นในการตรวจสอบเรื่องนี้และเพียงแค่ส่งคืนรายการเดียวกัน

ความกังวลคือบางครั้งคุณอาจต้องการให้แน่ใจอย่างแน่นอนว่ารายการที่ส่งคืนคือ! = ไปยังรายการดั้งเดิม เนื่องจาก Microsoft ไม่ได้เอกสารว่า ToList จะส่งคืนรายการใหม่เราจึงไม่แน่ใจ (เว้นแต่มีคนพบเอกสารนั้น) มันอาจเปลี่ยนแปลงได้ในอนาคตแม้ว่าจะใช้งานได้ในขณะนี้

รายชื่อใหม่ (IEnumerable enumerablestuff) รับประกันว่าจะส่งคืนรายการใหม่ ฉันจะใช้สิ่งนี้แทน


2
มันระบุไว้ในเอกสารประกอบที่นี่: " วิธีการ ToList <TSource> (IEnumerable <TSource>) บังคับให้มีการประเมินผลการสืบค้นทันทีและส่งคืนรายการ <T> ที่มีผลลัพธ์ของแบบสอบถามคุณสามารถผนวกวิธีการนี้กับแบบสอบถามของคุณเพื่อรับ สำเนาที่เก็บไว้ชั่วคราวของผลลัพธ์แบบสอบถาม "ประโยคที่สองยืนยันว่าการเปลี่ยนแปลงใด ๆ ในรายการต้นฉบับหลังจากการประเมินแบบสอบถามจะไม่มีผลกับรายการที่ส่งคืน
Raymond Chen

1
@RaymondChen สิ่งนี้เป็นจริงสำหรับ IEnumerables แต่ไม่ใช่สำหรับรายการ แม้ว่าToListดูเหมือนว่าจะสร้างการอ้างอิงวัตถุรายการใหม่เมื่อเรียกว่าในListBenB ถูกต้องเมื่อเขาบอกว่าสิ่งนี้ไม่รับประกันโดยเอกสาร MS
Teejay

@ RaymondChen ตามแหล่ง ChrisS IEnumerable<>.ToList()ถูกนำมาใช้จริงnew List<>(source)และไม่มีการแทนที่ที่เฉพาะเจาะจงList<>ดังนั้นจึงList<>.ToList()ส่งคืนการอ้างอิงวัตถุรายการใหม่ แต่อีกครั้งตามเอกสาร MS ไม่มีการรับประกันสำหรับสิ่งนี้ที่จะไม่เปลี่ยนแปลงในอนาคตแม้ว่ามันจะมีแนวโน้มที่จะทำลายรหัสส่วนใหญ่ออกไปที่นั่น
Teejay
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.