Tại sao nên sử dụng từ khóa 'ref' khi truyền đối tượng?


290

Nếu tôi chuyển một đối tượng cho một phương thức, tại sao tôi nên sử dụng từ khóa ref? Đây không phải là hành vi mặc định sao?

Ví dụ:

class Program
{
    static void Main(string[] args)
    {
        TestRef t = new TestRef();
        t.Something = "Foo";

        DoSomething(t);
        Console.WriteLine(t.Something);
    }

    static public void DoSomething(TestRef t)
    {
        t.Something = "Bar";
    }
}


public class TestRef
{
    public string Something { get; set; }
}

Đầu ra là "Bar" có nghĩa là đối tượng được truyền dưới dạng tham chiếu.

Câu trả lời:


298

Vượt qua refnếu bạn muốn thay đổi đối tượng là gì:

TestRef t = new TestRef();
t.Something = "Foo";
DoSomething(ref t);

void DoSomething(ref TestRef t)
{
  t = new TestRef();
  t.Something = "Not just a changed t, but a completely different TestRef object";
}

Sau khi gọi DoS Something, tkhông đề cập đến bản gốcnew TestRef , mà đề cập đến một đối tượng hoàn toàn khác.

Điều này cũng có thể hữu ích nếu bạn muốn thay đổi giá trị của một đối tượng bất biến, ví dụ a string. Bạn không thể thay đổi giá trị của một stringkhi nó đã được tạo. Nhưng bằng cách sử dụng a ref, bạn có thể tạo một hàm thay đổi chuỗi cho một chuỗi khác có giá trị khác.

Chỉnh sửa: Như những người khác đã đề cập. Nó không phải là một ý tưởng tốt để sử dụng reftrừ khi nó là cần thiết. Sử dụngref cho phép phương thức tự do thay đổi đối số cho một thứ khác, người gọi phương thức sẽ cần được mã hóa để đảm bảo họ xử lý khả năng này.

Ngoài ra, khi kiểu tham số là một đối tượng, thì các biến đối tượng luôn đóng vai trò tham chiếu đến đối tượng. Điều này có nghĩa là khi reftừ khóa được sử dụng, bạn đã có một tham chiếu đến một tham chiếu. Điều này cho phép bạn làm những việc như được mô tả trong ví dụ nêu trên. Nhưng, khi loại tham số là một giá trị nguyên thủy (ví dụ int), thì nếu tham số này được gán cho trong phương thức, giá trị của đối số được truyền vào sẽ được thay đổi sau khi phương thức trả về:

int x = 1;
Change(ref x);
Debug.Assert(x == 5);
WillNotChange(x);
Debug.Assert(x == 5); // Note: x doesn't become 10

void Change(ref int x)
{
  x = 5;
}

void WillNotChange(int x)
{
  x = 10;
}

88

Bạn cần phân biệt giữa "truyền tham chiếu theo giá trị" và "truyền tham số / đối số theo tham chiếu".

Tôi đã viết một bài viết khá dài về chủ đề này để tránh phải viết cẩn thận mỗi khi nó xuất hiện trên các nhóm tin :)


1
Vâng, tôi đã gặp vấn đề trong khi nâng cấp VB6 thành mã .Net C #. Có chữ ký hàm / phương thức lấy tham số ref, out và plain. Vậy làm thế nào chúng ta có thể phân biệt tốt hơn sự khác biệt giữa một param đơn giản và một ref?
bonCodigo 18/03/2015

2
@bonCodigo: Không chắc ý của bạn là "phân biệt rõ hơn" - đó là một phần của chữ ký và bạn cũng phải chỉ định reftại trang web cuộc gọi ... bạn còn muốn phân biệt ở đâu nữa không? Các ngữ nghĩa cũng rõ ràng hợp lý, nhưng cần phải được thể hiện một cách cẩn thận (chứ không phải là "các đối tượng được thông qua tham chiếu", đó là sự đơn giản hóa quá mức phổ biến).
Jon Skeet

tôi không biết tại sao phòng thu trực quan vẫn không hiển thị rõ ràng những gì đã qua
MonsterMMORPG

3
@MonsterMMORPG: Tôi không biết ý của bạn là gì, tôi sợ.
Jon Skeet

56

Trong .NET khi bạn truyền bất kỳ tham số nào cho một phương thức, một bản sao được tạo. Trong các loại giá trị có nghĩa là bất kỳ sửa đổi nào bạn thực hiện đối với giá trị đều nằm trong phạm vi phương thức và bị mất khi bạn thoát khỏi phương thức.

Khi chuyển Loại tham chiếu, một bản sao cũng được tạo, nhưng nó là bản sao của tham chiếu, tức là bây giờ bạn có HAI tham chiếu trong bộ nhớ cho cùng một đối tượng. Vì vậy, nếu bạn sử dụng tham chiếu để sửa đổi đối tượng, nó sẽ được sửa đổi. Nhưng nếu bạn sửa đổi chính tham chiếu - chúng ta phải nhớ đó là bản sao - thì mọi thay đổi cũng sẽ bị mất khi thoát khỏi phương thức.

Như mọi người đã nói trước đây, một bài tập là một sửa đổi của tài liệu tham khảo, do đó bị mất:

public void Method1(object obj) {   
 obj = new Object(); 
}

public void Method2(object obj) {  
 obj = _privateObject; 
}

Các phương pháp trên không sửa đổi đối tượng ban đầu.

Một chút sửa đổi ví dụ của bạn

 using System;

    class Program
        {
            static void Main(string[] args)
            {
                TestRef t = new TestRef();
                t.Something = "Foo";

                DoSomething(t);
                Console.WriteLine(t.Something);

            }

            static public void DoSomething(TestRef t)
            {
                t = new TestRef();
                t.Something = "Bar";
            }
        }



    public class TestRef
    {
    private string s;
        public string Something 
        { 
            get {return s;} 
            set { s = value; }
        }
    }

6
Tôi thích câu trả lời này tốt hơn sau đó câu trả lời được chấp nhận. Nó giải thích rõ ràng hơn những gì đang xảy ra khi chuyển một biến loại tham chiếu với từ khóa ref. Cảm ơn bạn!
Stefan

17

Vì TestRef là một lớp (là các đối tượng tham chiếu), bạn có thể thay đổi nội dung bên trong t mà không cần chuyển nó dưới dạng ref. Tuy nhiên, nếu bạn vượt qua t dưới dạng ref, TestRef có thể thay đổi nội dung mà t gốc đề cập đến. tức là làm cho nó trỏ đến một đối tượng khác.


16

Với refbạn có thể viết:

static public void DoSomething(ref TestRef t)
{
    t = new TestRef();
}

Và t sẽ được thay đổi sau khi phương thức đã hoàn thành.


8

Hãy nghĩ về các biến (ví dụ foo) của các loại tham chiếu (ví dụ List<T>) như giữ các định danh đối tượng có dạng "Đối tượng # 24601". Giả sử câu lệnh foo = new List<int> {1,5,7,9};gây ra foo"Giữ đối tượng # 24601" (một danh sách có bốn mục). Sau đó, việc gọi foo.Lengthsẽ hỏi Object # 24601 về độ dài của nó và nó sẽ trả lời 4, vì vậy foo.Lengthsẽ bằng 4.

Nếu foođược truyền cho một phương thức mà không sử dụng ref, phương thức đó có thể thay đổi đối tượng # 24601. Do hậu quả của những thay đổi như vậy, foo.Lengthcó thể không còn bằng 4. Tuy nhiên, bản thân phương thức sẽ không thể thay đổifoo , điều này sẽ tiếp tục giữ "Đối tượng # 24601".

Truyền foodưới dạng reftham số sẽ cho phép phương thức được gọi thực hiện thay đổi không chỉ đối tượng # 24601 mà còn cho foochính nó. Phương thức này có thể tạo Đối tượng mới # 8675309 và lưu trữ một tham chiếu đến đó foo. Nếu làm như vậy, foosẽ không còn giữ "Đối tượng # 24601", mà thay vào đó là "Đối tượng # 8675309".

Trong thực tế, các biến loại tham chiếu không giữ các chuỗi có dạng "Đối tượng # 8675309"; họ thậm chí không giữ bất cứ thứ gì có thể chuyển đổi thành số một cách có ý nghĩa. Mặc dù mỗi biến kiểu tham chiếu sẽ giữ một số mẫu bit, nhưng không có mối quan hệ cố định giữa các mẫu bit được lưu trữ trong các biến đó và các đối tượng mà chúng xác định. Không có cách nào mã có thể trích xuất thông tin từ một đối tượng hoặc một tham chiếu đến nó, và sau đó xác định xem một tham chiếu khác có xác định cùng một đối tượng hay không, trừ khi mã được giữ hoặc biết về một tham chiếu xác định đối tượng ban đầu.


5

Đây là giống như đi qua một con trỏ đến một con trỏ trong C. Trong NET này sẽ cho phép bạn thay đổi những gì T ban đầu đề cập đến, cá nhân mặc dù tôi nghĩ rằng nếu bạn đang làm điều đó trong .NET bạn đã có thể có một vấn đề thiết kế!


3

Bằng cách sử dụng reftừ khóa với các loại tham chiếu, bạn đang chuyển một tham chiếu đến tham chiếu một cách hiệu quả. Theo nhiều cách, nó giống như sử dụng outtừ khóa nhưng với sự khác biệt nhỏ là không có gì đảm bảo rằng phương thức sẽ thực sự gán bất cứ thứ gì cho reftham số 'ed.


3

ref bắt chước (hoặc hành xử) như một khu vực toàn cầu chỉ dành cho hai phạm vi:

  • Người gọi
  • Callee.

1

Nếu bạn đang vượt qua một giá trị, tuy nhiên, mọi thứ sẽ khác. Bạn có thể buộc một giá trị được chuyển qua tham chiếu. Điều này cho phép bạn chuyển một số nguyên cho một phương thức, ví dụ, và có phương thức thay đổi số nguyên thay cho bạn.


4
Cho dù bạn đang truyền tham chiếu hoặc giá trị loại giá trị, hành vi mặc định là truyền theo giá trị. Bạn chỉ cần hiểu rằng với các loại tham chiếu, giá trị bạn chuyển qua một tham chiếu. Đây không phải là giống như đi qua bằng cách tham khảo.
Jon Skeet

1

Ref biểu thị liệu hàm có thể chạm tay vào chính đối tượng đó hay chỉ dựa vào giá trị của nó.

Đi qua tham chiếu không bị ràng buộc với một ngôn ngữ; đó là một chiến lược ràng buộc tham số bên cạnh giá trị truyền qua, chuyển theo tên, chuyển qua nhu cầu, v.v ...

Một sidenote: tên lớp TestReflà một lựa chọn cực kỳ tồi tệ trong bối cảnh này;).

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.