รายการที่ส่งผ่านโดย ref - ช่วยฉันอธิบายพฤติกรรมนี้


110

ดูโปรแกรมต่อไปนี้:

class Test
{
    List<int> myList = new List<int>();

    public void TestMethod()
    {
        myList.Add(100);
        myList.Add(50);
        myList.Add(10);

        ChangeList(myList);

        foreach (int i in myList)
        {
            Console.WriteLine(i);
        }
    }

    private void ChangeList(List<int> myList)
    {
        myList.Sort();

        List<int> myList2 = new List<int>();
        myList2.Add(3);
        myList2.Add(4);

        myList = myList2;
    }
}

ฉันคิดว่าmyListน่าจะผ่านไปrefแล้วและผลลัพธ์ก็จะ

3
4

รายการนี้ "ส่งผ่านโดย ref" แต่เฉพาะsortฟังก์ชันเท่านั้นที่มีผล ข้อความต่อไปนี้myList = myList2;ไม่มีผล

ดังนั้นผลลัพธ์จึงเป็นจริง:

10
50
100

คุณช่วยอธิบายพฤติกรรมนี้ได้ไหม หากmyListไม่ผ่านการอ้างอิงจริง ๆ (ตามที่ดูเหมือนว่าmyList = myList2ไม่มีผล) จะmyList.Sort()มีผลอย่างไร?

ฉันคิดว่าแม้คำสั่งนั้นจะไม่มีผลและผลลัพธ์จะเป็น:

100
50
10

เป็นเพียงการสังเกต (และฉันรู้ว่าปัญหาได้ง่ายขึ้นที่นี่) แต่ดูเหมือนว่าจะเป็นการดีที่สุดที่ChangeListจะส่งคืนList<int>แทนที่จะเป็นvoidหากเป็นการสร้างรายการใหม่ในความเป็นจริง
Jeff B

คำตอบ:


111

คุณจะผ่านการอ้างอิงถึงรายการแต่คุณจะไม่ได้รับการส่งผ่านตัวแปรรายการโดยอ้างอิง - ดังนั้นเมื่อคุณโทรค่าของตัวแปร (เช่นอ้างอิง - คิดว่า "ตัวชี้") จะถูกคัดลอก - และการเปลี่ยนแปลงค่าของ พารามิเตอร์ภายในไม่ได้มองเห็นได้ด้วยChangeListChangeList TestMethod

ลอง:

private void ChangeList(ref List<int> myList) {...}
...
ChangeList(ref myList);

จากนั้นจะส่งการอ้างอิงไปยังตัวแปรท้องถิ่น myRef (ตามที่ประกาศไว้TestMethod) ตอนนี้หากคุณกำหนดพารามิเตอร์ใหม่ภายในChangeListคุณกำลังกำหนดตัวแปรภายใน TestMethodอีกครั้ง


อันที่จริงฉันทำได้ แต่ฉันอยากรู้ว่าการเรียงลำดับมีผลอย่างไร
nmdr

6
@Ngm - เมื่อคุณโทรChangeList, เพียงการอ้างอิงจะถูกคัดลอก - มันเป็นวัตถุเดียวกัน หากคุณเปลี่ยนวัตถุไม่ทางใดก็ทางหนึ่งทุกสิ่งที่มีการอ้างอิงถึงวัตถุนั้นจะเห็นการเปลี่ยนแปลง
Marc Gravell

225

ในขั้นต้นสามารถแสดงเป็นกราฟิกได้ดังนี้:

สถานะเริ่มต้น

จากนั้นจึงใช้การเรียงลำดับ myList.Sort(); จัดเรียงคอลเลกชัน

สุดท้ายเมื่อคุณทำ: myList' = myList2คุณสูญเสียข้อมูลอ้างอิง แต่ไม่ใช่ต้นฉบับและคอลเล็กชันยังคงเรียงลำดับ

ข้อมูลอ้างอิงหายไป

ถ้าคุณใช้โดยการอ้างอิง ( ref) แล้วmyList'และmyListจะกลายเป็นเหมือนกัน (เพียงหนึ่งอ้างอิง)

หมายเหตุ: ฉันใช้myList'เพื่อแสดงพารามิเตอร์ที่คุณใช้ChangeList(เพราะคุณตั้งชื่อเดียวกันกับต้นฉบับ)


20

นี่คือวิธีง่ายๆในการทำความเข้าใจ

  • รายการของคุณเป็นวัตถุที่สร้างขึ้นบนฮีป ตัวแปรmyListคือการอ้างอิงถึงวัตถุนั้น

  • ใน C # คุณไม่เคยผ่านวัตถุคุณส่งต่อการอ้างอิงตามค่า

  • เมื่อคุณเข้าถึงออบเจ็กต์รายการผ่านการอ้างอิงที่ผ่านใน ChangeList(ในขณะที่เรียงลำดับเป็นต้น) รายการต้นฉบับจะเปลี่ยนไป

  • การกำหนดChangeListเมธอดจะทำกับค่าของการอ้างอิงดังนั้นจึงไม่มีการเปลี่ยนแปลงใด ๆ กับรายการเดิม (ยังคงอยู่ในฮีป แต่ไม่ได้อ้างถึงตัวแปรวิธีการอีกต่อไป)


10

ลิงค์นี้จะช่วยคุณในการทำความเข้าใจโดยอ้างอิงใน C # โดยทั่วไปเมื่อออบเจ็กต์ประเภทการอ้างอิงถูกส่งผ่านค่าไปยังเมธอดเมธอดที่มีอยู่บนอ็อบเจ็กต์นั้นเท่านั้นที่สามารถแก้ไขเนื้อหาของอ็อบเจ็กต์ได้

ตัวอย่างเช่นวิธี List.sort () จะเปลี่ยนเนื้อหารายการ แต่ถ้าคุณกำหนดอ็อบเจ็กต์อื่นให้กับตัวแปรเดียวกันการกำหนดนั้นจะเป็นแบบโลคัลสำหรับเมธอดนั้น นั่นคือเหตุผลที่ myList ยังคงไม่เปลี่ยนแปลง

หากเราส่งออบเจ็กต์ประเภทการอ้างอิงโดยใช้คีย์เวิร์ดอ้างอิงเราสามารถกำหนดอ็อบเจกต์อื่นให้กับตัวแปรเดียวกันและเปลี่ยนอ็อบเจ็กต์ทั้งหมดเอง

(แก้ไข: นี่คือเวอร์ชันอัปเดตของเอกสารที่ลิงก์ด้านบน)


5

C # ทำสำเนาตื้น ๆ เมื่อผ่านค่าเว้นแต่ว่าวัตถุที่มีปัญหาจะดำเนินการICloneable(ซึ่งเห็นได้ชัดว่าListคลาสไม่มี)

สิ่งนี้หมายความว่ามันคัดลอกListตัวมันเอง แต่การอ้างอิงถึงวัตถุภายในรายการยังคงเหมือนเดิม Listที่เป็นตัวชี้ยังคงอ้างอิงวัตถุเดียวกันเช่นเดิม

หากคุณเปลี่ยนค่าของสิ่งที่คุณListอ้างอิงใหม่คุณก็จะเปลี่ยนค่าเดิมListด้วย (เนื่องจากอ้างอิงอ็อบเจ็กต์เดียวกัน) อย่างไรก็ตามคุณเปลี่ยนmyListการอ้างอิงทั้งหมดเป็นแบบใหม่Listและตอนนี้มีเพียงต้นฉบับเท่านั้นที่Listอ้างอิงจำนวนเต็มเหล่านั้น

อ่านหัวข้อPassing Reference-Type Parametersจากบทความ MSDN เรื่อง "Passing Parameters"สำหรับข้อมูลเพิ่มเติม

"ฉันจะโคลนรายการทั่วไปใน C #"จาก StackOverflow พูดถึงวิธีการสร้างสำเนาแบบลึกของรายการ


3

ในขณะที่ฉันเห็นด้วยกับสิ่งที่ทุกคนได้กล่าวไว้ข้างต้น ฉันใช้รหัสนี้ที่แตกต่างออกไป โดยทั่วไปคุณกำลังกำหนดรายการใหม่ให้กับ myList ตัวแปรภายในไม่ใช่ส่วนกลาง หากคุณเปลี่ยนลายเซ็นของ ChangeList (List myList) เป็นโมฆะส่วนตัว ChangeList () คุณจะเห็นผลลัพธ์เป็น 3, 4

นี่คือเหตุผลของฉัน ... แม้ว่ารายการจะถูกส่งผ่านโดยการอ้างอิง แต่ให้คิดว่ามันส่งผ่านตัวแปรตัวชี้ตามค่าเมื่อคุณเรียก ChangeList (myList) คุณจะส่งตัวชี้ไปยัง myList (Global) ตอนนี้สิ่งนี้ถูกเก็บไว้ในตัวแปร myList (ในเครื่อง) ตอนนี้ myList (local) และ myList (global) ของคุณกำลังชี้ไปที่รายการเดียวกัน ตอนนี้คุณทำการ sort => ใช้งานได้เนื่องจาก myList (local) กำลังอ้างอิง myList ดั้งเดิม (ส่วนกลาง) ถัดไปคุณจะสร้างรายการใหม่และกำหนดตัวชี้ให้กับ myList (ในเครื่อง) ของคุณ แต่ทันทีที่ฟังก์ชันออกจากตัวแปร myList (ในเครื่อง) จะถูกทำลาย HTH

class Test
{
    List<int> myList = new List<int>();
    public void TestMethod()
    {

        myList.Add(100);
        myList.Add(50);
        myList.Add(10);

        ChangeList();

        foreach (int i in myList)
        {
            Console.WriteLine(i);
        }
    }

    private void ChangeList()
    {
        myList.Sort();

        List<int> myList2 = new List<int>();
        myList2.Add(3);
        myList2.Add(4);

        myList = myList2;
    }
}

3

ใช้refคำหลัก

ดูข้อมูลอ้างอิงขั้นสุดท้ายที่นี่เพื่อทำความเข้าใจพารามิเตอร์การส่งผ่าน
หากต้องการเจาะจงให้ดูที่สิ่งนี้เพื่อทำความเข้าใจลักษณะการทำงานของโค้ด

แก้ไข: Sortทำงานบนการอ้างอิงเดียวกัน (ที่ส่งผ่านด้วยค่า) และด้วยเหตุนี้ค่าจึงถูกเรียงลำดับ refอย่างไรก็ตามการกำหนดตัวอย่างใหม่ให้กับพารามิเตอร์จะไม่ทำงานเพราะพารามิเตอร์ผ่านค่าเว้นแต่คุณใส่

การใส่refช่วยให้คุณเปลี่ยนตัวชี้เป็นการอ้างอิงไปยังอินสแตนซ์ใหม่Listในกรณีของคุณ หากไม่มีrefคุณสามารถทำงานกับพารามิเตอร์ที่มีอยู่ได้ แต่ไม่สามารถชี้ไปที่อย่างอื่นได้


0

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

เมื่อไม่ได้ใช้คีย์เวิร์ด ref จะมีการสร้างสำเนาของส่วนในสแตกและส่งต่อไปยังเมธอด - อ้างอิงไปยังส่วนเดียวกันในฮีป ดังนั้นหากคุณเปลี่ยนบางสิ่งในส่วนฮีปการเปลี่ยนแปลงเหล่านั้นจะยังคงอยู่ หากคุณเปลี่ยนตัวชี้ที่คัดลอก - โดยกำหนดให้อ้างถึงตำแหน่งอื่นในฮีปจะไม่มีผลกับตัวชี้ต้นทางนอกเมธอด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.