Khi nào sử dụng ref và khi nào không cần thiết trong C #


104

Tôi có một đối tượng là trạng thái bộ nhớ của chương trình và cũng có một số hàm worker khác mà tôi truyền đối tượng để sửa đổi trạng thái. Tôi đã chuyển nó bằng ref đến các hàm worker. Tuy nhiên tôi đã xem qua chức năng sau đây.

byte[] received_s = new byte[2048];
IPEndPoint tmpIpEndPoint = new IPEndPoint(IPAddress.Any, UdpPort_msg);
EndPoint remoteEP = (tmpIpEndPoint);

int sz = soUdp_msg.ReceiveFrom(received_s, ref remoteEP); 

Nó làm tôi bối rối vì cả hai received_sremoteEPđang trả về nội dung từ hàm. Tại sao remoteEPcần a refreceived_skhông?

Tôi cũng là lập trình viên ac vì vậy tôi đang gặp vấn đề với việc lấy con trỏ ra khỏi đầu.

Chỉnh sửa: Có vẻ như các đối tượng trong C # là các con trỏ đến đối tượng bên dưới. Vì vậy, khi bạn truyền một đối tượng cho một hàm thì bạn có thể sửa đổi nội dung đối tượng thông qua con trỏ và thứ duy nhất được truyền cho hàm là con trỏ tới đối tượng để bản thân đối tượng không bị sao chép. Bạn sử dụng ref hoặc out nếu bạn muốn có thể chuyển đổi hoặc tạo một đối tượng mới trong hàm giống như một con trỏ kép.

Câu trả lời:


216

Câu trả lời ngắn gọn: hãy đọc bài viết của tôi về cách vượt qua đối số .

Câu trả lời dài: khi một tham số kiểu tham chiếu được truyền theo giá trị, chỉ tham chiếu được truyền, không phải bản sao của đối tượng. Điều này giống như truyền một con trỏ (theo giá trị) trong C hoặc C ++. Bản thân người gọi sẽ không nhìn thấy các thay đổi đối với giá trị của tham số, nhưng các thay đổi trong đối tượng mà tham chiếu trỏ đến sẽ được nhìn thấy.

Khi một tham số (thuộc bất kỳ loại nào) được truyền bằng tham chiếu, điều đó có nghĩa là bất kỳ thay đổi nào đối với tham số đều được người gọi nhìn thấy - những thay đổi đối với tham số những thay đổi đối với biến.

Tất nhiên, bài báo giải thích tất cả những điều này một cách chi tiết hơn :)

Câu trả lời hữu ích: bạn hầu như không bao giờ cần sử dụng ref / out . Về cơ bản, đó là một cách để nhận một giá trị trả về khác và thường nên tránh chính xác vì nó có nghĩa là phương thức có thể đang cố gắng làm quá nhiều. Không phải lúc nào cũng vậy ( TryParsevv là những ví dụ điển hình về việc sử dụng hợp lý out) nhưng việc sử dụng ref / out sẽ là một điều tương đối hiếm.


38
Tôi nghĩ bạn đã có câu trả lời ngắn và câu trả lời dài lẫn lộn; đó là một bài báo lớn!
Outlaw Programmer

23
@Outlaw: Vâng, nhưng câu trả lời ngắn chính nó, chỉ thị để đọc bài viết này, chỉ dài 6 chữ :)
Jon Skeet

27
Một tài liệu tham khảo! :)
gonzobrains 13/12/12

5
@Liam Sử dụng ref như bạn làm có thể thấy rõ ràng hơn đối với bạn, nhưng nó thực sự có thể gây nhầm lẫn cho các lập trình viên khác (những người biết từ khóa đó làm gì), bởi vì về cơ bản bạn đang nói với những người gọi tiềm năng, "Tôi có thể sửa đổi biến mà bạn được sử dụng trong phương thức gọi, tức là gán lại nó cho một đối tượng khác (hoặc thậm chí là null, nếu có thể) vì vậy đừng bám vào nó, hoặc đảm bảo rằng bạn xác thực nó khi tôi hoàn thành nó ". Điều đó khá mạnh và hoàn toàn khác với "đối tượng này có thể được sửa đổi", điều này luôn xảy ra bất cứ khi nào bạn chuyển một tham chiếu đối tượng làm tham số.
mbargiel

1
@ M.Mimpen: C # 7 sẽ (hy vọng) cho phépif (int.TryParse(text, out int value)) { ... use value here ... }
Jon Skeet

26

Hãy coi tham số không phải ref là một con trỏ và một tham số ref là một con trỏ kép. Điều này đã giúp tôi nhiều nhất.

Bạn hầu như không bao giờ chuyển các giá trị bằng ref. Tôi nghi ngờ rằng nếu không phải vì mối quan tâm về tương tác, nhóm .Net sẽ không bao giờ đưa nó vào đặc tả ban đầu. Cách OO đối phó với hầu hết các vấn đề mà tham số ref giải quyết là:

Đối với nhiều giá trị trả về

  • Tạo cấu trúc đại diện cho nhiều giá trị trả về

Đối với các nguyên thủy thay đổi trong một phương thức do kết quả của lệnh gọi phương thức (phương thức có tác dụng phụ đối với các tham số nguyên thủy)

  • Triển khai phương thức trong một đối tượng dưới dạng phương thức thể hiện và thao tác trạng thái của đối tượng (không phải các tham số) như một phần của lệnh gọi phương thức
  • Sử dụng giải pháp nhiều giá trị trả về và hợp nhất các giá trị trả về vào trạng thái của bạn
  • Tạo một đối tượng chứa trạng thái có thể được thao tác bởi một phương thức và chuyển đối tượng đó làm tham số, chứ không phải bản thân các nguyên thủy.

8
Chúa ơi. Tôi phải đọc cái này 20 lần để hiểu nó. Nghe có vẻ như một đống công việc đối với tôi chỉ để làm một cái gì đó đơn giản.
Tích

Khuôn khổ .NET không tuân theo quy tắc số 1 của bạn. ('Đối với nhiều giá trị trả về, hãy tạo cấu trúc'). Lấy ví dụ IPAddress.TryParse(string, out IPAddress).
Swen Kooij

@SwenKooij Bạn nói đúng. Tôi giả định rằng ở hầu hết những nơi mà họ sử dụng các tham số thì (a) Họ đang gói một API Win32 hoặc (b) đó là những ngày đầu và các lập trình viên C ++ đang đưa ra quyết định.
Michael Meadows

@SwenKooij Tôi biết câu trả lời này đã muộn nhiều năm, nhưng nó đúng. Chúng ta đã quen với TryParse, nhưng không có nghĩa là nó tốt. Sẽ tốt hơn nếu thay vì if (int.TryParse("123", out var theInt) { /* use theInt */ }chúng ta có var candidate = int.TrialParse("123"); if (candidate.Parsed) { /* do something with candidate.Value */ }Nó là nhiều mã hơn, nhưng phù hợp hơn nhiều với thiết kế ngôn ngữ C #.
Michael Meadows

9

Bạn có thể viết toàn bộ ứng dụng C # và không bao giờ chuyển bất kỳ đối tượng / cấu trúc nào bằng ref.

Tôi có một giáo sư nói với tôi điều này:

Nơi duy nhất bạn sử dụng refs là nơi bạn:

  1. Muốn chuyển một đối tượng lớn (tức là các đối tượng / cấu trúc có các đối tượng / cấu trúc bên trong nó sang nhiều cấp) và sao chép nó sẽ rất tốn kém và
  2. Bạn đang gọi một Framework, Windows API hoặc API khác yêu cầu nó.

Đừng làm điều đó chỉ vì bạn có thể. Bạn có thể gặp phải một số lỗi khó chịu nếu bạn bắt đầu thay đổi các giá trị trong một tham số và không chú ý.

Tôi đồng ý với lời khuyên của anh ấy và trong hơn 5 năm kể từ khi đi học, tôi chưa bao giờ có nhu cầu về nó ngoài việc gọi Framework hoặc Windows API.


3
Nếu bạn định triển khai "Hoán đổi", việc chuyển qua các giới thiệu có thể hữu ích.
Brian

@Chris có vấn đề gì không nếu tôi sử dụng từ khóa ref cho các đối tượng nhỏ?
ManirajSS

@TechnikEmpire "Tuy nhiên, các thay đổi đối với đối tượng trong phạm vi của hàm được gọi không được phản ánh lại cho người gọi." Đó là sai lầm. Nếu tôi chuyển một Người cho SetName(person, "John Doe"), thuộc tính tên sẽ thay đổi và thay đổi đó sẽ được phản ánh cho người gọi.
M. Mimpen

@ M.Mimpen Bình luận đã bị xóa. Tôi hầu như không chọn được C # vào thời điểm đó và rõ ràng là đang nói ra * $$ của mình. Cám ơn bạn đã khiến tôi chú ý tới việc này.

@Chris - Tôi khá chắc chắn rằng việc chuyển một đối tượng "lớn" không tốn kém. Nếu bạn chỉ chuyển nó theo giá trị, bạn vẫn chỉ truyền một con trỏ duy nhất phải không?
Ian

3

Vì Recei_s là một mảng, bạn đang chuyển một con trỏ đến mảng đó. Hàm điều khiển dữ liệu hiện có đó tại chỗ, không thay đổi vị trí hoặc con trỏ bên dưới. Từ khóa ref cho biết rằng bạn đang chuyển con trỏ thực tế đến vị trí và cập nhật con trỏ đó trong hàm bên ngoài, vì vậy giá trị trong hàm bên ngoài sẽ thay đổi.

Vd: mảng byte là một con trỏ đến cùng một vùng nhớ trước sau, vùng nhớ vừa được cập nhật.

Tham chiếu Điểm cuối thực sự đang cập nhật con trỏ tới Điểm cuối trong hàm bên ngoài thành một thể hiện mới được tạo bên trong hàm.


3

Hãy nghĩ về một tham chiếu có nghĩa là bạn đang chuyển một con trỏ bằng tham chiếu. Không sử dụng tham chiếu có nghĩa là bạn đang chuyển một con trỏ theo giá trị.

Tốt hơn hết, hãy bỏ qua những gì tôi vừa nói (nó có thể gây hiểu lầm, đặc biệt là với các loại giá trị) và đọc trang MSDN này .


Trên thực tế, không đúng. Ít nhất là phần thứ hai. Mọi loại tham chiếu sẽ luôn được chuyển bằng tham chiếu, cho dù bạn có sử dụng tham chiếu hay không.
Erik Funkenbusch

Trên thực tế, khi suy ngẫm thêm, điều đó không đúng. Tham chiếu thực sự được truyền theo giá trị mà không có kiểu tham chiếu. Có nghĩa là, việc thay đổi các giá trị được trỏ đến bởi tham chiếu sẽ thay đổi dữ liệu gốc, nhưng bản thân việc thay đổi tham chiếu không thay đổi tham chiếu ban đầu.
Erik Funkenbusch

2
Loại tham chiếu không phải là tham chiếu không được chuyển bằng tham chiếu. Một tham chiếu đến kiểu tham chiếu được chuyển theo giá trị. Nhưng nếu bạn muốn coi một tham chiếu như một con trỏ đến một thứ đang được tham chiếu, thì những gì tôi đã nói có lý (nhưng nghĩ theo cách đó có thể gây hiểu lầm). Do đó cảnh báo của tôi.
Brian

Link là chết - hãy thử một này để thay thế - docs.microsoft.com/en-us/dotnet/csharp/language-reference/...
GIVE-ME-GÀ

0

sự hiểu biết của tôi là tất cả các đối tượng bắt nguồn từ lớp Đối tượng được chuyển dưới dạng con trỏ trong khi các kiểu thông thường (int, struct) không được chuyển dưới dạng con trỏ và yêu cầu tham chiếu. Tôi cũng không chắc về chuỗi (cuối cùng thì nó có nguồn gốc từ lớp Đối tượng không?)


Điều này có thể sử dụng một số làm rõ. Sự khác biệt giữa tyo giá trị và tham chiếu là gì. Trả lời tại sao điều đó có liên quan đến việc sử dụng từ khóa ref trên một tham số?
oɔɯǝɹ

Trong .net, mọi thứ ngoại trừ con trỏ, tham số kiểu và giao diện đều có nguồn gốc từ Object. Điều quan trọng cần hiểu là các kiểu "thông thường" (được gọi chính xác là "kiểu giá trị") cũng kế thừa từ đối tượng. Bạn đã đúng từ đó: Các loại giá trị (theo mặc định) được truyền theo giá trị, trong khi các loại tham chiếu được chuyển bằng tham chiếu. Nếu bạn muốn sửa đổi một kiểu giá trị, thay vì trả về một kiểu mới (xử lý nó giống như kiểu tham chiếu bên trong một phương thức), bạn sẽ phải sử dụng từ khóa ref trên đó. Nhưng đó là phong cách xấu và bạn chỉ không nên làm điều đó trừ khi bạn hoàn toàn chắc chắn rằng bạn phải :)
buddybubble

1
để trả lời câu hỏi của bạn: chuỗi có nguồn gốc từ đối tượng. Nó là một kiểu tham chiếu hoạt động giống như một kiểu giá trị (vì lý do hiệu suất và logic)
buddybubble

0

Mặc dù tôi đồng ý với câu trả lời của Jon Skeet nói chung và một số câu trả lời khác, nhưng có một trường hợp sử dụng để sử dụng refvà đó là để thắt chặt tối ưu hóa hiệu suất. Người ta đã quan sát thấy trong quá trình lập hồ sơ hiệu suất rằng việc đặt giá trị trả về của một phương thức có tác động nhỏ đến hiệu suất, trong khi việc sử dụng reflàm đối số theo đó giá trị trả về được điền vào tham số đó dẫn đến việc loại bỏ nút cổ chai nhỏ này.

Điều này thực sự chỉ hữu ích khi các nỗ lực tối ưu hóa được thực hiện ở mức độ cao nhất, hy sinh khả năng đọc và có thể là khả năng kiểm tra và khả năng bảo trì để tiết kiệm mili giây hoặc có thể là chia nhỏ mili giây.


-1

Trước tiên, quy tắc số không cơ bản, Nguyên thủy được truyền theo giá trị (ngăn xếp) và Không nguyên thủy bằng tham chiếu (Heap) trong ngữ cảnh của TYPES liên quan.

Các tham số liên quan được truyền theo Giá trị theo mặc định. Bài đăng tốt giải thích mọi thứ chi tiết. http://yoda.arachsys.com/csharp/parameters.html

Student myStudent = new Student {Name="A",RollNo=1};

ChangeName(myStudent);

static void ChangeName(Student s1)
{
  s1.Name = "Z"; // myStudent.Name will also change from A to Z
                // {AS s1 and myStudent both refers to same Heap(Memory)
                //Student being the non-Primitive type
}

ChangeNameVersion2(ref myStudent);
static void ChangeNameVersion2(ref Student s1)
{
  s1.Name = "Z"; // Not any difference {same as **ChangeName**}
}

static void ChangeNameVersion3(ref Student s1)
{
    s1 = new Student{Name="Champ"};

    // reference(myStudent) will also point toward this new Object having new memory
    // previous mystudent memory will be released as it is not pointed by any object
}

Chúng ta có thể nói (với cảnh báo) Các kiểu không nguyên thủy không là gì khác ngoài Con trỏ Và khi chúng ta chuyển chúng bằng ref, chúng ta có thể nói rằng chúng ta đang truyền Con trỏ kép


Tất cả các tham số được truyền theo giá trị, theo mặc định, trong C #. Bất kỳ tham số nào cũng có thể được truyền bằng tham chiếu. Đối với các loại tham chiếu, giá trị được truyền (bằng tham chiếu hoặc theo giá trị) chính nó là một tham chiếu. Điều đó hoàn toàn độc lập với cách nó được thông qua.
Servy

Đồng ý @Servy! "Khi chúng ta nghe thấy các từ" tham chiếu "hoặc" giá trị "được sử dụng, chúng ta nên nhớ rất rõ ràng rằng chúng ta muốn nói rằng một tham số là một tham chiếu hay một tham số giá trị hay ý chúng ta muốn nói rằng loại có liên quan là một tham chiếu hay loại giá trị 'Little Về phía tôi có sự nhầm lẫn, TẠI SAO tôi đã nói quy tắc Ground zero đầu tiên, Nguyên thủy được truyền theo giá trị (ngăn xếp) và Không nguyên thủy bằng tham chiếu (Heap) Tôi có nghĩa là từ TYPES liên quan, không phải tham số! Khi chúng ta nói về Tham số, Bạn đã đúng , tất cả các tham số được truyền theo giá trị, theo mặc định trong C #.
Surender Singh Malik

Nói rằng một tham số là một tham chiếu hoặc tham số giá trị không phải là một thuật ngữ tiêu chuẩn và thực sự không rõ ràng về ý của bạn. Kiểu của tham số có thể là kiểu tham chiếu hoặc kiểu giá trị. Bất kỳ tham số nào cũng có thể được truyền theo giá trị hoặc tham chiếu. Đây là những khái niệm trực giao cho bất kỳ tham số nhất định nào. Bài đăng của bạn mô tả sai điều này.
Servy

1
Bạn chuyển các tham số theo tham chiếu hoặc theo giá trị. Các thuật ngữ "theo tham chiếu" và "theo giá trị" không được sử dụng để mô tả một kiểu là kiểu giá trị hay kiểu tham chiếu.
Servy

1
Không, vấn đề không phải là nhận thức. Khi bạn sử dụng thuật ngữ không chính xác để chỉ điều gì đó thì câu nói đó sẽ trở thành sai . Điều quan trọng đối với các phát biểu đúng là sử dụng thuật ngữ chính xác để chỉ các khái niệm.
Servy
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.