Danh sách được thông qua bởi ref - giúp tôi giải thích hành vi này


109

Hãy xem chương trình sau:

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

Tôi cho rằng myListsẽ trôi qua refvà kết quả đầu ra sẽ

3
4

Danh sách thực sự là "được thông qua bởi ref", nhưng chỉ sorthàm có hiệu lực. Câu lệnh sau myList = myList2;không có hiệu lực.

Vì vậy, đầu ra trên thực tế là:

10
50
100

Bạn có thể giúp tôi giải thích hành vi này không? Nếu thực sự myListkhông phải là ref-by-ref (vì nó có vẻ như myList = myList2không có hiệu lực), làm thế nào myList.Sort()có hiệu lực?

Tôi đã giả định rằng ngay cả câu lệnh đó cũng không có hiệu lực và kết quả đầu ra là:

100
50
10

Chỉ là một quan sát (và tôi nhận ra vấn đề đã được đơn giản hóa ở đây), nhưng có vẻ như tốt nhất ChangeListlà trả về một List<int>thay vì là một voidnếu thực tế là tạo một danh sách mới.
Jeff B

Câu trả lời:


110

Bạn đang đi qua một tham chiếu đến trong danh sách , nhưng bạn không được đi qua các biến danh sách bằng cách tham khảo - vì vậy khi bạn gọi ChangeListcác giá trị của biến (tức là tài liệu tham khảo - nghĩ "con trỏ") được sao chép - và thay đổi đối với giá trị của tham số bên trong ChangeList không được nhìn thấy bởi TestMethod.

thử:

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

Sau đó, điều này sẽ chuyển một tham chiếu đến biến cục bộ myRef (như được khai báo trong TestMethod); bây giờ, nếu bạn gán lại tham số bên trong, ChangeListbạn cũng đang gán lại biến bên trong TestMethod .


Trên thực tế tôi có thể làm điều đó, nhưng tôi muốn biết làm thế nào loại được có hiệu lực
nmdr

6
@Ngm - khi bạn gọi ChangeList, chỉ tham chiếu được sao chép - nó là cùng một đối tượng. Nếu bạn thay đổi đối tượng theo một cách nào đó, mọi thứ có tham chiếu đến đối tượng đó sẽ thấy sự thay đổi.
Marc Gravell

225

Ban đầu, nó có thể được biểu diễn bằng đồ thị như sau:

Init bang

Sau đó, sắp xếp được áp dụng myList.Sort(); Sắp xếp bộ sưu tập

Cuối cùng, khi bạn đã làm:, myList' = myList2bạn đã mất một trong các tham chiếu nhưng không phải là bản gốc và bộ sưu tập vẫn được sắp xếp.

Tham chiếu bị mất

Nếu bạn sử dụng bằng tham chiếu ( ref) thì myList'myListsẽ trở nên giống nhau (chỉ một tham chiếu).

Lưu ý: Tôi sử dụng myList'để đại diện cho tham số mà bạn sử dụng ChangeList(vì bạn đã đặt tên giống với tên gốc)


20

Đây là một cách dễ hiểu về nó

  • Danh sách của bạn là một đối tượng được tạo trên heap. Biến myListlà một tham chiếu đến đối tượng đó.

  • Trong C #, bạn không bao giờ chuyển các đối tượng, bạn chuyển các tham chiếu của chúng theo giá trị.

  • Khi bạn truy cập đối tượng danh sách thông qua tham chiếu được truyền vào ChangeList(ví dụ: trong khi sắp xếp), danh sách ban đầu được thay đổi.

  • Việc gán trên ChangeListphương thức được thực hiện cho giá trị của tham chiếu, do đó không có thay đổi nào được thực hiện đối với danh sách ban đầu (vẫn ở trên heap nhưng không được tham chiếu trên biến phương thức nữa).


10

Đây liên kết sẽ giúp bạn trong việc tìm hiểu thông qua bằng cách tham chiếu trong C #. Về cơ bản, khi một đối tượng của kiểu tham chiếu được truyền bằng giá trị cho một phương thức, thì chỉ những phương thức có sẵn trên đối tượng đó mới có thể sửa đổi nội dung của đối tượng.

Ví dụ, phương thức List.sort () thay đổi nội dung danh sách nhưng nếu bạn gán một số đối tượng khác cho cùng một biến, thì phép gán đó là cục bộ cho phương thức đó. Đó là lý do tại sao myList vẫn không thay đổi.

Nếu chúng ta chuyển đối tượng của kiểu tham chiếu bằng cách sử dụng từ khóa ref thì chúng ta có thể gán một số đối tượng khác cho cùng một biến và điều đó sẽ thay đổi toàn bộ đối tượng.

(Chỉnh sửa: đây là phiên bản cập nhật của tài liệu được liên kết ở trên.)


5

C # chỉ thực hiện một bản sao cạn khi nó đi qua giá trị trừ khi đối tượng được đề cập thực thi ICloneable(mà rõ ràng là Listlớp không thực thi).

Điều này có nghĩa là nó sao chép Listchính nó, nhưng các tham chiếu đến các đối tượng bên trong danh sách vẫn giữ nguyên; nghĩa là, các con trỏ tiếp tục tham chiếu đến các đối tượng giống như ban đầu List.

Nếu bạn thay đổi giá trị của những thứ mà bạn Listtham chiếu mới , bạn cũng thay đổi giá trị ban đầu List(vì nó đang tham chiếu đến những đối tượng giống nhau). Tuy nhiên, sau đó bạn thay đổi myListhoàn toàn những gì tham chiếu sang một tham chiếu mới Listvà bây giờ chỉ bản gốc Listtham chiếu đến những số nguyên đó.

Đọc phần Tham số kiểu tham chiếu chuyển từ bài viết MSDN này về "Tham số chuyển" để biết thêm thông tin.

"Làm cách nào để sao chép một danh sách chung trong C #" từ StackOverflow nói về cách tạo bản sao sâu của một danh sách.


3

Trong khi tôi đồng ý với những gì mọi người đã nói ở trên. Tôi có một cách khác về mã này. Về cơ bản, bạn đang gán danh sách mới cho biến cục bộ myList không phải là toàn cục. nếu bạn thay đổi chữ ký của ChangeList (List myList) thành private void ChangeList (), bạn sẽ thấy kết quả là 3, 4.

Đây là lý do của tôi ... Mặc dù danh sách được truyền bằng tham chiếu, hãy nghĩ về nó như là truyền một biến con trỏ theo giá trị Khi bạn gọi ChangeList (myList), bạn đang chuyển con trỏ tới (Global) myList. Bây giờ điều này được lưu trữ trong biến myList (cục bộ). Vì vậy, bây giờ myList (cục bộ) và myList (toàn cầu) của bạn đang trỏ đến cùng một danh sách. Bây giờ bạn thực hiện sắp xếp => nó hoạt động vì myList (cục bộ) đang tham chiếu đến myList gốc (toàn cầu) Tiếp theo, bạn tạo một danh sách mới và gán con trỏ cho myList (cục bộ) của bạn. Nhưng ngay sau khi hàm thoát ra, biến myList (cục bộ) sẽ bị hủy. 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;
    }
}

2

Sử dụng reftừ khóa.

Nhìn vào tham chiếu cuối cùng ở đây để hiểu các tham số truyền.
Để cụ thể hơn, hãy nhìn vào phần này , để hiểu hành vi của mã.

EDIT: Sorthoạt động trên cùng một tham chiếu (được truyền theo giá trị) và do đó các giá trị được sắp xếp theo thứ tự. Tuy nhiên, việc gán một phiên bản mới cho tham số sẽ không hoạt động vì tham số được truyền theo giá trị, trừ khi bạn đặt ref.

Putting refcho phép bạn thay đổi con trỏ tham chiếu đến một phiên bản mới Listtrong trường hợp của bạn. Nếu không có ref, bạn có thể làm việc trên tham số hiện có, nhưng không thể làm cho nó trỏ đến một thứ khác.


0

Có hai phần bộ nhớ được cấp phát cho một đối tượng kiểu tham chiếu. Một trong chồng và một trong đống. Phần trong ngăn xếp (hay còn gọi là con trỏ) chứa tham chiếu đến phần trong heap - nơi các giá trị thực được lưu trữ.

Khi từ khóa ref không được sử dụng, chỉ một bản sao của một phần trong ngăn xếp được tạo và chuyển đến phương thức - tham chiếu đến cùng một phần trong heap. Vì vậy, nếu bạn thay đổi một cái gì đó trong phần heap, những thay đổi đó sẽ vẫn còn. Nếu bạn thay đổi con trỏ đã sao chép - bằng cách gán nó để tham chiếu đến vị trí khác trong heap - nó sẽ không ảnh hưởng đến con trỏ gốc bên ngoài phương thức.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.