2008-10-09 27 views
204

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

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à các đối tượng đã được thông qua như một tài liệu tham khảo.

Trả lời

209

Vượt qua một ref nếu bạn muốn thay đổi những gì đối tượng là:

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 DoSomething, t không đề cập đến bản gốc new TestRef, nhưng đề cập đến một đối tượng hoàn toàn khác nhau.

Điều này cũng 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 string khi nó đã được tạo. Nhưng bằng cách sử dụng ref, bạn có thể tạo một hàm thay đổi chuỗi cho 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 ref trừ khi nó là cần thiết. Sử dụng ref cho phép phương thức tự do thay đổi đối số cho cái gì khác, người gọi của 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 loại tham số là đối tượng, thì các biến đối tượng luôn hoạt động như tham chiếu đến đối tượng. Điều này có nghĩa là khi từ khóa ref được sử dụng, bạn có tham chiếu đến 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ụ ở trên. Tuy nhiên, khi các loại tham số là một giá trị nguyên thủy (ví dụ int), sau đó nếu tham số này được gán cho trong phương pháp này, giá trị của lập luận rằng đã được thông qua sẽ được thay đổi sau khi trở về phương pháp:

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

Nếu bạn đang chuyển một giá trị, tuy nhiên, mọi thứ khác nhau. 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 vào 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 chuyển một giá trị tham chiếu hay giá trị loại giá trị, hành vi mặc định sẽ được chuyể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 đang truyền * là * một tham chiếu. Điều này không giống như tham chiếu * by *. –

13

Với ref bạ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 pháp này đã hoàn thành.

16

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 chuyển nó thành một ref. Tuy nhiên, nếu bạn chuyển t thành ref, TestRef có thể thay đổi t gốc ban đầu. tức là làm cho nó trỏ đến một vật thể khác.

3

Bằng cách sử dụng từ khóa ref 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 từ khóa out nhưng với sự khác biệt nhỏ mà không đảm bảo rằng phương thức sẽ thực sự gán bất kỳ thứ gì cho thông số ed ref '.

3

Điều này giống như chuyển một con trỏ tới con trỏ trong C. Trong .NET, điều 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 đó. một vấn đề thiết kế!

1

Ref biểu thị xem chức năng có thể tự tay lấy đối tượng hay chỉ trên giá trị của nó.

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

Mặt khác: tên lớp TestRef là một lựa chọn không tốt trong bối cảnh này;).

69

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

Tôi đã viết một reasonably long article on the subject để tránh phải viết cẩn thận mỗi lần này đi lên trên nhóm tin :)

+1

Tôi cũng gặp phải vấn đề này trong khi nâng cấp VB6 thành mã .Net C#. Có các chữ ký hàm/phương thức có 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 thông thường so với ref? – bonCodigo

+1

@bonCodigo: Không chắc chắn ý bạn là gì "phân biệt tốt hơn" - đó là một phần của chữ ký và bạn cũng phải chỉ định 'ref' tại trang web cuộc gọi ... bạn muốn phân biệt ở đâu khác? Các ngữ nghĩa cũng rõ ràng một cách 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 truyền qua tham chiếu", đó là sự đơn giản hóa quá mức). –

+0

tôi không biết tại sao visual studio vẫn không hiển thị rõ ràng những gì được thông qua – MonsterMMORPG

21

Trong NET khi bạn vượt qua bất kỳ tham số để một phương pháp, một bản sao được tạo ra. 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 cho 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, bản sao cũng được tạo, nhưng nó là bản sao tham chiếu, tức là 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 tham chiếu chính nó - chúng ta phải nhớ nó là một bản sao - sau đó bất kỳ thay đổi nào cũng bị mất khi thoát khỏi phương thức.

Khi mọi người đã nói trước đây, một bài tập là một sự sửa đổi của tham chiếu, 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 làm thay đổi đối tượng gốc.

Một sửa đổi chút 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; } 
     } 
    } 
+1

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

3

ref bắt chước (hoặc cư xử) như là một khu vực toàn cầu chỉ dành riêng cho hai lĩnh vực:

  • Caller
  • callee.
6

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

Nếu foo được chuyển đến một phương pháp mà không sử dụng ref, phương pháp mà có thể thực hiện thay đổi để Object # 24.601. Kết quả của những thay đổi như vậy, foo.Length có thể không còn bằng 4. Tuy nhiên, phương pháp này sẽ không thể thay đổi foo, sẽ tiếp tục giữ "Đối tượng # 24601".

Chuyển foo thành thông số ref sẽ cho phép phương pháp được gọi để thực hiện thay đổi không chỉ đối với đối tượng # 24601, mà còn cho chính mình là foo. Phương pháp này có thể tạo đối tượng mới # 8675309 và lưu trữ tham chiếu đến đối tượng đó trong foo. Nếu có, foo sẽ không còn giữ "Đối tượng # 24601", mà thay vào đó là "Object # 8675309".

Trong thực tế, các biến kiểu tham chiếu không giữ các chuỗi có dạng "Object # 8675309"; họ thậm chí không giữ bất cứ thứ gì có thể chuyển đổi thành một số. Mặc dù mỗi biến kiểu tham chiếu sẽ giữ một số mẫu bit, 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 như vậy và các đối tượng mà chúng xác định. Không có cách nào có thể trích xuất thông tin từ một đối tượng hoặc 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 một tham chiếu xác định đối tượng gốc.

Các vấn đề liên quan