Việc sử dụng từ chối giới thiệu cho các biến kiểu tham chiếu trong C # là gì?


176

Tôi hiểu rằng nếu tôi chuyển một loại giá trị ( int, structv.v.) làm tham số (không có reftừ khóa), một bản sao của biến đó sẽ được truyền cho phương thức, nhưng nếu tôi sử dụng reftừ khóa thì tham chiếu đến biến đó sẽ được thông qua, không phải là một cái mới

Nhưng với các kiểu tham chiếu, như các lớp, thậm chí không có reftừ khóa, một tham chiếu được truyền cho phương thức, không phải là một bản sao. Vì vậy, việc sử dụng reftừ khóa với các loại tham chiếu là gì?


Lấy ví dụ:

var x = new Foo();

Sự khác biệt giữa những điều sau đây là gì?

void Bar(Foo y) {
    y.Name = "2";
}

void Bar(ref Foo y) {
    y.Name = "2";
}

Câu trả lời:


154

Bạn có thể thay đổi những foođiểm cần sử dụng y:

Foo foo = new Foo("1");

void Bar(ref Foo y)
{
    y = new Foo("2");
}

Bar(ref foo);
// foo.Name == "2"

17
vì vậy về cơ bản bạn có được một tài liệu tham khảo cho tài liệu tham khảo ban đầu
lhahne

2
Bạn có thể thay đổi những gì tham chiếu ban đầu 'đề cập', vì vậy, có.
dùng7116

1
Chris, lời giải thích của bạn là tuyệt vời; Cảm ơn đã giúp tôi hiểu khái niệm này.
Andreas Grech

4
Vì vậy, sử dụng 'ref' trên một đối tượng cũng giống như sử dụng con trỏ kép trong C ++?
Tom Hazel

1
@TomHazel: -ish , miễn là bạn đang sử dụng con trỏ "nhân đôi" trong C ++ để thay đổi những gì con trỏ trỏ tới.
dùng7116

29

Có những trường hợp bạn muốn sửa đổi tham chiếu thực tế và không phải đối tượng được trỏ đến:

void Swap<T>(ref T x, ref T y) {
    T t = x;
    x = y;
    y = t;
}

var test = new[] { "0", "1" };
Swap(ref test[0], ref test[1]);

21

Jon Skeet đã viết một bài viết tuyệt vời về thông số truyền trong C #. Nó chi tiết rõ ràng hành vi chính xác và cách sử dụng các tham số truyền theo giá trị, theo tham chiếu ( ref) và theo đầu ra ( out).

Đây là một trích dẫn quan trọng từ trang đó liên quan đến refcác tham số:

Tham số tham chiếu không vượt qua các giá trị của các biến được sử dụng trong lệnh gọi thành viên hàm - chúng sử dụng chính các biến đó. Thay vì tạo một vị trí lưu trữ mới cho biến trong khai báo thành viên hàm, cùng một vị trí lưu trữ được sử dụng, do đó, giá trị của biến trong thành viên hàm và giá trị của tham số tham chiếu sẽ luôn giống nhau. Tham số tham chiếu cần công cụ sửa đổi ref như một phần của cả khai báo và lời gọi - điều đó có nghĩa là nó luôn rõ ràng khi bạn chuyển một cái gì đó bằng tham chiếu.


11
Tôi thích sự tương tự của việc đưa dây xích chó của bạn cho bạn bè để chuyển một giá trị tham chiếu ... mặc dù nó bị hỏng nhanh chóng, bởi vì tôi nghĩ bạn thể sẽ chú ý nếu bạn của bạn trao đổi shitzu của bạn với một doberman trước khi anh ta đưa bạn trở lại dây xích ;-)
corlettk

16

Giải thích rất độc đáo ở đây: http://msdn.microsoft.com/en-us/l Library / s6938f28.aspx

Tóm tắt từ bài viết:

Một biến của kiểu tham chiếu không chứa dữ liệu của nó trực tiếp; nó chứa một tham chiếu đến dữ liệu của nó. Khi bạn truyền tham số kiểu tham chiếu theo giá trị, có thể thay đổi dữ liệu được trỏ đến bởi tham chiếu, chẳng hạn như giá trị của thành viên lớp. Tuy nhiên, bạn không thể thay đổi giá trị của chính tham chiếu; nghĩa là, bạn không thể sử dụng cùng một tham chiếu để phân bổ bộ nhớ cho một lớp mới và để nó tồn tại bên ngoài khối. Để làm điều đó, vượt qua tham số bằng cách sử dụng từ khóa ref hoặc out.


4
Lời giải thích thực sự rất hay. Tuy nhiên, câu trả lời chỉ liên kết không được khuyến khích trên SO. Tôi đã thêm một bản tóm tắt từ bài viết, như một sự thuận tiện cho độc giả ở đây.
Marcel

10

Khi bạn chuyển một loại tham chiếu với từ khóa ref, bạn chuyển tham chiếu theo tham chiếu và phương thức bạn gọi có thể gán giá trị mới cho tham số. Sự thay đổi đó sẽ lan truyền đến phạm vi gọi. Không có ref, tham chiếu được truyền theo giá trị và điều này không xảy ra.

C # cũng có từ khóa 'out' rất giống với ref, ngoại trừ với 'ref', các đối số phải được khởi tạo trước khi gọi phương thức và với 'out', bạn phải gán giá trị trong phương thức nhận.


5

Nó cho phép bạn sửa đổi các tham chiếu được truyền vào. Vd

void Bar()
{
    var y = new Foo();
    Baz(ref y);
}

void Baz(ref Foo y)
{
    y.Name = "2";

    // Overwrite the reference
    y = new Foo();
}

Bạn cũng có thể sử dụng ra nếu bạn không quan tâm đến các tài liệu tham khảo thông qua tại:

void Bar()
{
    var y = new Foo();
    Baz(out y);
}

void Baz(out Foo y)
{
    // Return a new reference
    y = new Foo();
}

4

Một bó mã khác

class O
{
    public int prop = 0;
}

class Program
{
    static void Main(string[] args)
    {
        O o1 = new O();
        o1.prop = 1;

        O o2 = new O();
        o2.prop = 2;

        o1modifier(o1);
        o2modifier(ref o2);

        Console.WriteLine("1 : " + o1.prop.ToString());
        Console.WriteLine("2 : " + o2.prop.ToString());
        Console.ReadLine();
    }

    static void o1modifier(O o)
    {
        o = new O();
        o.prop = 3;
    }

    static void o2modifier(ref O o)
    {
        o = new O();
        o.prop = 4;
    }
}

3

Ngoài các câu trả lời hiện có:

Như bạn đã hỏi về sự khác biệt của 2 phương thức: Không có phương sai co (ntra) khi sử dụng refhoặc out:

class Foo { }
class FooBar : Foo { }

static void Bar(Foo foo) { }
static void Bar(ref Foo foo) { foo = new Foo(); }

void Main()
{
    Foo foo = null;
    Bar(foo);           // OK
    Bar(ref foo);       // OK

    FooBar fooBar = null;
    Bar(fooBar);        // OK (covariance)
    Bar(ref fooBar);    // compile time error
}

1

Một tham số trong một phương thức dường như luôn luôn truyền một bản sao, câu hỏi là một bản sao của cái gì. Một bản sao được thực hiện bởi một hàm tạo sao chép cho một đối tượng và vì tất cả các biến là Object trong C #, tôi tin rằng đây là trường hợp của tất cả chúng. Biến (đối tượng) giống như người sống ở một số địa chỉ. Chúng tôi hoặc thay đổi những người sống ở những địa chỉ đó hoặc chúng tôi có thể tạo thêm tài liệu tham khảo cho những người sống ở những địa chỉ đó trong danh bạ điện thoại (tạo các bản sao nông). Vì vậy, nhiều hơn một định danh có thể tham chiếu đến cùng một địa chỉ. Các kiểu tham chiếu mong muốn nhiều không gian hơn, vì vậy không giống như các loại giá trị được kết nối trực tiếp bằng một mũi tên với mã định danh của chúng trong ngăn xếp, chúng có giá trị cho một địa chỉ khác trong heap (một không gian lớn hơn để trú ngụ). Không gian này cần phải được lấy từ đống.

Loại giá trị: Indentifier (chứa value = địa chỉ của giá trị ngăn xếp) ----> Giá trị của loại giá trị

Kiểu tham chiếu: Mã định danh (chứa giá trị = địa chỉ của giá trị ngăn xếp) ----> (chứa giá trị = địa chỉ của giá trị heap) ----> Giá trị heap (thường chứa địa chỉ cho các giá trị khác), hãy tưởng tượng nhiều mũi tên khác nhau chỉ đường đến Mảng [0], Mảng [1], mảng [2]

Cách duy nhất để thay đổi một giá trị là theo các mũi tên. Nếu một mũi tên bị mất / thay đổi theo cách giá trị không thể truy cập được.


-1

Các biến tham chiếu mang địa chỉ từ nơi này đến nơi khác để mọi cập nhật về chúng tại bất kỳ nơi nào sẽ phản ánh trên tất cả các địa điểm THÌ việc sử dụng REF là gì. Biến tham chiếu (405) là tốt cho đến khi không có bộ nhớ mới được phân bổ cho biến tham chiếu được truyền trong phương thức.

Khi bộ nhớ mới phân bổ (410) thì giá trị thay đổi trên đối tượng này (408) sẽ không phản ánh ở mọi nơi. Đối với ref này đến. Tham chiếu là tham chiếu của tham chiếu, do đó, bất cứ khi nào bộ nhớ mới phân bổ, bạn sẽ biết vì nó đang trỏ đến vị trí đó do đó giá trị có thể được chia sẻ bởi mọiOne. Bạn có thể xem hình ảnh cho rõ ràng hơn.

Tham chiếu biến tham chiếu

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.