Sự khác biệt giữa các từ khóa 'ref' và 'out' là gì?


891

Tôi đang tạo một hàm trong đó tôi cần truyền một đối tượng để nó có thể được sửa đổi bởi hàm đó. Sự khác biệt giữa:

public void myFunction(ref MyClass someClass)

public void myFunction(out MyClass someClass)

Tôi nên sử dụng cái gì và tại sao?


69
Bạn: Tôi cần phải vượt qua một đối tượng để nó có thể được sửa đổi Có vẻ như MyClassđó sẽ là một classloại, tức là một loại tham chiếu. Trong trường hợp đó, đối tượng bạn vượt qua có thể được sửa đổi bằng cách myFunctionkhông có ref/ outtừ khóa. myFunctionsẽ nhận được một tham chiếu mới trỏ đến cùng một đối tượng và nó có thể sửa đổi cùng một đối tượng đó theo ý muốn. Sự khác biệt mà reftừ khóa sẽ tạo ra, sẽ là myFunctionnhận được cùng một tham chiếu đến cùng một đối tượng. Điều đó chỉ quan trọng nếu myFunctionđược thay đổi tham chiếu để trỏ đến một đối tượng khác .
Jeppe Stig Nielsen

3
Tôi bối rối trước số lượng câu trả lời khó hiểu ở đây, khi @ AnthonyKolesov khá hoàn hảo.
o0 '.

Khai báo một phương thức out là hữu ích khi bạn muốn một phương thức trả về nhiều giá trị. Một đối số có thể được gán cho null. Điều này cho phép các phương thức trả về giá trị tùy chọn.
Yevgraf Andreyevich Zhivago

Ở đây giải thích với Ví dụ Điều này dễ hiểu hơn :) dotnet-tricks.com/Tutorial/csharp/ Kẻ
Ca ngợi thần thánh

2
Nhận xét của @ JeppeStigNielsen về mặt kỹ thuật là câu trả lời đúng (duy nhất) cho câu hỏi thực tế của OP. Để truyền một đối tượng vào một phương thức để phương thức có thể sửa đổi đối tượng , chỉ cần truyền đối tượng (tham chiếu đến) vào phương thức theo giá trị. Thay đổi đối tượng trong phương thức thông qua đối số đối tượng sẽ sửa đổi đối tượng ban đầu , mặc dù phương thức chứa biến riêng của nó (tham chiếu cùng một đối tượng).
David R Tribble

Câu trả lời:


1160

refbáo cho trình biên dịch rằng đối tượng được khởi tạo trước khi vào hàm, trong khi báo outcho trình biên dịch rằng đối tượng sẽ được khởi tạo bên trong hàm.

Vì vậy, trong khi reflà hai cách, outlà chỉ ra.


270
Một điều thú vị cụ thể khác là hàm phải gán cho tham số out. Nó không được phép để nó không được chỉ định.
Daniel Earwicker

7
là 'ref' chỉ áp dụng cho loại giá trị? Vì kiểu tham chiếu luôn được truyền bởi ref.
bị lỗi

3
Đúng. Các loại giá trị bao gồm cả cấu trúc
Rune Grimstad

17
@faulty: Không, ref không chỉ áp dụng cho các loại giá trị. ref / out giống như các con trỏ trong C / C ++, chúng xử lý vị trí bộ nhớ của đối tượng (gián tiếp trong C #) thay vì đối tượng trực tiếp.
ném

52
@faulty: Ngược lại, các loại Tham chiếu luôn được truyền theo giá trị trong C #, trừ khi bạn sử dụng công cụ xác định ref. Nếu bạn đặt myval = somenewval, hiệu ứng chỉ trong phạm vi chức năng đó. Từ khóa ref sẽ cho phép bạn thay đổi myval để trỏ đến somenewval.
JasonTrue

535

Công cụ refsửa đổi có nghĩa là:

  1. Giá trị đã được đặt và
  2. Phương pháp có thể đọc và sửa đổi nó.

Công cụ outsửa đổi có nghĩa là:

  1. Giá trị không được đặt và phương thức không thể đọc được cho đến khi được đặt.
  2. Phương thức phải đặt nó trước khi quay trở lại.

30
Câu trả lời này giải thích rõ ràng và chính xác nhất các hạn chế mà trình biên dịch áp đặt khi sử dụng từ khóa out trái ngược với từ khóa ref.
Học viên của Tiến sĩ Wily

5
Từ MSDN: Tham số ref phải được khởi tạo trước khi sử dụng, trong khi tham số out không phải được khởi tạo rõ ràng trước khi được truyền và bất kỳ giá trị nào trước đó đều bị bỏ qua.
Shiva Kumar

1
Với out, nó có thể được đọc ở tất cả trong phương thức không, trước khi nó được đặt bởi phương thức đó, nếu nó đã được khởi tạo trước khi phương thức được gọi? Ý tôi là, phương thức được gọi có thể đọc những gì phương thức gọi được truyền cho nó như là một đối số không?
Panzercrisis

3
Panzercrisis, đối với "out", phương thức được gọi có thể đọc nếu nó đã được đặt. nhưng nó phải đặt lại.
robert jebakumar2

146

Giả sử, Dom xuất hiện tại phòng của Peter về bản ghi nhớ về các báo cáo TPS.

Nếu Dom là một đối số ref, anh ta sẽ có một bản in của bản ghi nhớ.

Nếu Dom là một cuộc tranh cãi, anh ta sẽ khiến Peter in một bản sao mới của bản ghi nhớ để anh ta mang theo.


54
ref Dom sẽ viết báo cáo bằng bút chì để Peter có thể sửa đổi nó
Deebster

6
@Deebster bạn biết đấy, phép ẩn dụ đó không bao giờ làm gì bạn, tại sao bạn phải hành hạ nó như vậy? ;)
Michael Blackburn

21
giải trí nhưng mang tính giáo dục, stackoverflow cần nhiều bài đăng như thế này
Frank Visaggio

2
Chỉ trong trường hợp ai đó thấy câu trả lời này chỉ nửa hài hước, vui lòng xem phim "Không gian văn phòng".
displayName

và ông chủ của Dom và Peters sẽ đứng ra đối phó với Dom (như tranh luận), buộc cả hai phải làm việc để in nó ra một lần nữa cho đến khi Peter trao cho Domd bản in
Patrick Artner

57

Tôi sẽ cố gắng giải thích:

Tôi nghĩ rằng chúng ta hiểu làm thế nào các loại giá trị hoạt động phải không? Các loại giá trị là (int, long, struct, v.v.). Khi bạn gửi chúng đến một hàm mà không có lệnh ref, nó sẽ sao chép dữ liệu . Bất cứ điều gì bạn làm với dữ liệu đó trong hàm chỉ ảnh hưởng đến bản sao chứ không ảnh hưởng đến bản gốc. Lệnh ref gửi dữ liệu THỰC TẾ và mọi thay đổi sẽ ảnh hưởng đến dữ liệu bên ngoài chức năng.

Ok về phần khó hiểu, các loại tham khảo:

Cho phép tạo một kiểu tham chiếu:

List<string> someobject = new List<string>()

Khi bạn mới lên một số dự án , hai phần được tạo:

  1. Khối bộ nhớ chứa dữ liệu cho someobject .
  2. Một tham chiếu (con trỏ) đến khối dữ liệu đó.

Bây giờ khi bạn gửi một số đối tượng vào một phương thức mà không tham chiếu , nó sẽ sao chép con trỏ tham chiếu , KHÔNG phải dữ liệu. Vì vậy, bây giờ bạn có điều này:

(outside method) reference1 => someobject
(inside method)  reference2 => someobject

Hai tham chiếu trỏ đến cùng một đối tượng. Nếu bạn sửa đổi một thuộc tính trên someobject bằng cách sử dụng Reference2, nó sẽ ảnh hưởng đến cùng một dữ liệu được chỉ ra bởi tham chiếu1 .

 (inside method)  reference2.Add("SomeString");
 (outside method) reference1[0] == "SomeString"   //this is true

Nếu bạn bỏ tham chiếu2 hoặc trỏ nó vào dữ liệu mới, nó sẽ không ảnh hưởng đến tham chiếu1 cũng như tham chiếu dữ liệu1 trỏ đến.

(inside method) reference2 = new List<string>();
(outside method) reference1 != null; reference1[0] == "SomeString" //this is true

The references are now pointing like this:
reference2 => new List<string>()
reference1 => someobject

Bây giờ điều gì xảy ra khi bạn gửi một số dự án bằng cách tham chiếu đến một phương thức? Các tài liệu tham khảo thực tế để SomeObject được gửi đến phương pháp này. Vì vậy, bây giờ bạn chỉ có một tham chiếu đến dữ liệu:

(outside method) reference1 => someobject;
(inside method)  reference1 => someobject;

Nhưng điều này có nghĩa là gì? Nó hoạt động chính xác giống như gửi một số dự án không phải bằng ref ngoại trừ hai điều chính:

1) Khi bạn null tham chiếu bên trong phương thức, nó sẽ null tham chiếu bên ngoài phương thức.

 (inside method)  reference1 = null;
 (outside method) reference1 == null;  //true

2) Bây giờ bạn có thể trỏ tham chiếu đến một vị trí dữ liệu hoàn toàn khác và tham chiếu bên ngoài chức năng bây giờ sẽ trỏ đến vị trí dữ liệu mới.

 (inside method)  reference1 = new List<string>();
 (outside method) reference1.Count == 0; //this is true

Ý bạn là sau tất cả (trong trường hợp ref) chỉ có một tham chiếu đến dữ liệu nhưng có hai bí danh cho nó. Đúng?
Sadiq

3
Nâng cao cho lời giải thích rõ ràng. Nhưng tôi nghĩ rằng điều này không trả lời câu hỏi, vì nó không giải thích sự khác biệt giữa refoutcác tham số.
Joyce Babu

1
Kinh ngạc. bạn có thể giải thích tương tự như cho outtừ khóa?
Asif Mushtaq

28

ref là trong ngoài .

Bạn nên sử dụng outưu tiên bất cứ nơi nào nó đáp ứng yêu cầu của bạn.


không hoàn toàn, như câu trả lời được chấp nhận tham chiếu nếu định hướng và vô dụng bỏ qua các loại giá trị nếu không được đưa ra ngoài.
kenny

@kenny: Bạn có thể làm rõ một chút xin vui lòng - tức là, những từ nào bạn sẽ thay đổi để duy trì tinh thần của câu trả lời nhưng loại bỏ sự thiếu hiểu biết mà bạn nhận được? Câu trả lời của tôi không phải là một phỏng đoán điên rồ từ một người mới, nhưng sự vội vàng (lỗi, lỗi chính tả) trong bình luận của bạn dường như cho rằng nó là. Mục đích là để cung cấp một cách suy nghĩ về sự khác biệt với số lượng từ ít nhất.
Ruben Bartelink

(BTW Tôi quen thuộc với các loại giá trị, loại tham chiếu, chuyển qua tham chiếu, chuyển qua giá trị, COM và C ++ nếu bạn thấy hữu ích khi tham chiếu đến các khái niệm đó trong phần làm rõ của mình)
Ruben Bartelink

1
Tham chiếu đối tượng được truyền theo giá trị (trừ khi sử dụng từ khóa "ref" hoặc "out"). Hãy nghĩ về các đối tượng như số ID. Nếu một biến lớp giữ "Đối tượng # 1943" và một biến vượt qua biến đó theo giá trị cho một thường trình, thì thường trình đó có thể thay đổi đối tượng # 1943, nhưng nó không thể biến biến đó thành bất kỳ thứ gì ngoài "Đối tượng # 1943". Nếu biến được truyền bằng tham chiếu, thường trình có thể làm cho điểm biến giữ "Đối tượng # 5441".
supercat

1
@supercat: Tôi thích lời giải thích của bạn về ref vs val (và điều này theo dõi giải phẫu học). Tôi nghĩ rằng kenny thực sự không cần bất kỳ điều này giải thích cho anh ta, (tương đối) khó hiểu như những bình luận của anh ta. Tôi ước rằng tất cả chúng ta có thể loại bỏ những bình luận chết tiệt này vì chúng chỉ khiến mọi người bối rối. Nguyên nhân sâu xa của tất cả những điều vô nghĩa này dường như là việc kenny đọc sai câu trả lời của tôi và vẫn chưa chỉ ra một từ nào cần được thêm / xóa / thay thế. Không ai trong ba chúng tôi đã học được bất cứ điều gì từ cuộc thảo luận mà chúng tôi chưa biết và câu trả lời khác có số lượng upvote lố bịch.
Ruben Bartelink

18

ngoài:

Trong C #, một phương thức chỉ có thể trả về một giá trị. Nếu bạn muốn trả về nhiều hơn một giá trị, bạn có thể sử dụng từ khóa out. Công cụ sửa đổi out trả về dưới dạng tham chiếu trả về. Câu trả lời đơn giản nhất là từ khóa ra ngoài ra được sử dụng để lấy giá trị từ phương thức.

  1. Bạn không cần phải khởi tạo giá trị trong chức năng gọi.
  2. Bạn phải gán giá trị trong hàm được gọi, nếu không trình biên dịch sẽ báo lỗi.

tham chiếu:

Trong C #, khi bạn truyền một loại giá trị như int, float, double, v.v. làm đối số cho tham số phương thức, nó được truyền theo giá trị. Do đó, nếu bạn sửa đổi giá trị tham số, nó không ảnh hưởng đến đối số trong lệnh gọi phương thức. Nhưng nếu bạn đánh dấu tham số bằng từ khóa của ref ref, nó sẽ phản ánh trong biến thực tế.

  1. Bạn cần khởi tạo biến trước khi gọi hàm.
  2. Không bắt buộc phải gán bất kỳ giá trị nào cho tham số ref trong phương thức. Nếu bạn không thay đổi giá trị, bạn cần đánh dấu nó là gì?

"Trong C #, một phương thức chỉ có thể trả về một giá trị. Nếu bạn muốn trả về nhiều hơn một giá trị, bạn có thể sử dụng từ khóa out." Chúng tôi cũng có thể sử dụng "ref" để trả về giá trị. Vì vậy, chúng ta có thể sử dụng cả ref và out nếu chúng ta muốn trả về nhiều giá trị từ một phương thức?
Ned

1
Trong c # 7, bạn có thể trả về nhiều giá trị với ValueTuples.
Iman Bahrampour

13

Mở rộng ví dụ Dog, Cat. Phương thức thứ hai với ref thay đổi đối tượng được tham chiếu bởi người gọi. Do đó "Cát" !!!

    public static void Foo()
    {
        MyClass myObject = new MyClass();
        myObject.Name = "Dog";
        Bar(myObject);
        Console.WriteLine(myObject.Name); // Writes "Dog". 
        Bar(ref myObject);
        Console.WriteLine(myObject.Name); // Writes "Cat". 
    }

    public static void Bar(MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

    public static void Bar(ref MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

8

Vì bạn đang chuyển qua một loại tham chiếu (một lớp) nên không cần sử dụng refvì mỗi mặc định chỉ có một tham chiếu đến đối tượng thực tế được truyền và do đó bạn luôn thay đổi đối tượng đằng sau tham chiếu.

Thí dụ:

public void Foo()
{
    MyClass myObject = new MyClass();
    myObject.Name = "Dog";
    Bar(myObject);
    Console.WriteLine(myObject.Name); // Writes "Cat".
}

public void Bar(MyClass someObject)
{
    someObject.Name = "Cat";
}

Miễn là bạn vượt qua trong một lớp bạn không phải sử dụng refnếu bạn muốn thay đổi đối tượng bên trong phương thức của mình.


5
Điều này chỉ hoạt động nếu không có đối tượng mới được tạo và trả lại. Khi một đối tượng mới được tạo, tham chiếu đến đối tượng cũ sẽ bị mất.
etsuba

8
Điều này là sai - thử làm như sau: thêm someObject = nullvào Barkết thúc thực hiện. Mã của bạn sẽ chạy tốt vì chỉ Bartham chiếu đến thể hiện đã bị hủy. Bây giờ thay đổi Barđể Bar(ref MyClass someObject)và thực hiện một lần nữa - bạn sẽ nhận được một NullReferenceExceptionFoo's tham chiếu đến các trường hợp đã được nulled quá.
Keith

8

refouthành xử tương tự ngoại trừ sau sự khác biệt.

  • refbiến phải được khởi tạo trước khi sử dụng. outbiến có thể được sử dụng mà không cần gán
  • outtham số phải được coi là một giá trị chưa được gán bởi hàm sử dụng nó. Vì vậy, chúng ta có thể sử dụng outtham số khởi tạo trong mã gọi, nhưng giá trị sẽ bị mất khi hàm thực thi.


6

"Thợ làm bánh"

Đó là bởi vì cái đầu tiên thay đổi tham chiếu chuỗi của bạn để trỏ đến "Baker". Có thể thay đổi tham chiếu vì bạn đã chuyển nó qua từ khóa ref (=> tham chiếu đến tham chiếu đến chuỗi). Cuộc gọi thứ hai nhận được một bản sao của tham chiếu đến chuỗi.

chuỗi trông một số loại đặc biệt lúc đầu. Nhưng chuỗi chỉ là một lớp tham chiếu và nếu bạn định nghĩa

string s = "Able";

thì s là một tham chiếu đến một lớp chuỗi có chứa văn bản "Có thể"! Một nhiệm vụ khác cho cùng một biến thông qua

s = "Baker";

không thay đổi chuỗi gốc mà chỉ tạo một thể hiện mới và hãy trỏ đến thể hiện đó!

Bạn có thể thử nó với ví dụ mã nhỏ sau đây:

string s = "Able";
string s2 = s;
s = "Baker";
Console.WriteLine(s2);

Bạn mong chờ điều gì? Những gì bạn sẽ nhận được vẫn là "Có thể" vì bạn chỉ đặt tham chiếu trong s thành một thể hiện khác trong khi s2 trỏ đến thể hiện ban đầu.

EDIT: chuỗi cũng không thay đổi, điều đó có nghĩa là đơn giản là không có phương thức hoặc thuộc tính nào sửa đổi một thể hiện chuỗi hiện có (bạn có thể cố gắng tìm một trong các tài liệu nhưng bạn sẽ không hoàn thành bất kỳ :-)). Tất cả các phương thức thao tác chuỗi trả về một thể hiện chuỗi mới! (Đó là lý do tại sao bạn thường có hiệu suất tốt hơn khi sử dụng lớp StringBuilder)


1
Chính xác. Vì vậy, không hoàn toàn đúng khi nói "Vì bạn đang chuyển qua loại tham chiếu (một lớp) nên không cần sử dụng ref".
Paul Mitchell

Về lý thuyết, việc nói như vậy là đúng bởi vì ông đã viết "để nó có thể được sửa đổi" mà không thể thực hiện được trên chuỗi. Nhưng vì các đối tượng bất biến "ref" và "out" cũng rất hữu ích cho các loại tham chiếu! (.Net chứa rất nhiều lớp bất biến!)
mmmmmmmm

Vâng bạn đã đúng. Tôi đã không nghĩ về các đối tượng bất biến như các chuỗi bởi vì hầu hết các đối tượng là có thể thay đổi.
Albic

1
Vâng, đây là một câu trả lời khó hiểu để xem trong LQP, để chắc chắn; không có gì sai với nó ngoại trừ việc nó dường như là một câu trả lời dài và kỹ lưỡng cho một bình luận khác (vì câu hỏi ban đầu đề cập đến Able và Baker trong bất kỳ sửa đổi nào của nó), như thể đây là một diễn đàn. Tôi đoán rằng nó đã không thực sự được sắp xếp từ khi nào.
Nathan Tuggy

6

ref có nghĩa là giá trị trong tham số ref đã được đặt, phương thức có thể đọc và sửa đổi nó. Sử dụng từ khóa ref cũng giống như nói rằng người gọi có trách nhiệm khởi tạo giá trị của tham số.


out nói với trình biên dịch rằng việc khởi tạo đối tượng là trách nhiệm của hàm, hàm phải gán cho tham số out. Nó không được phép để nó không được chỉ định.


5

Out: Câu lệnh return có thể được sử dụng để chỉ trả về một giá trị từ hàm. Tuy nhiên, bằng cách sử dụng các tham số đầu ra, bạn có thể trả về hai giá trị từ một hàm. Các tham số đầu ra giống như các tham số tham chiếu, ngoại trừ việc chúng truyền dữ liệu ra khỏi phương thức chứ không phải vào nó.

Ví dụ sau đây minh họa điều này:

using System;

namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void getValue(out int x )
      {
         int temp = 5;
         x = temp;
      }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;

         Console.WriteLine("Before method call, value of a : {0}", a);

         /* calling a function to get the value */
         n.getValue(out a);

         Console.WriteLine("After method call, value of a : {0}", a);
         Console.ReadLine();

      }
   }
}

ref: Tham số tham chiếu là tham chiếu đến vị trí bộ nhớ của biến. Khi bạn truyền tham số bằng tham chiếu, không giống như tham số giá trị, vị trí lưu trữ mới không được tạo cho các tham số này. Các tham số tham chiếu biểu thị cùng một vị trí bộ nhớ với các tham số thực được cung cấp cho phương thức.

Trong C #, bạn khai báo các tham số tham chiếu bằng từ khóa ref. Ví dụ sau đây minh chứng điều này:

using System;
namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void swap(ref int x, ref int y)
      {
         int temp;

         temp = x; /* save the value of x */
         x = y;   /* put y into x */
         y = temp; /* put temp into y */
       }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;
         int b = 200;

         Console.WriteLine("Before swap, value of a : {0}", a);
         Console.WriteLine("Before swap, value of b : {0}", b);

         /* calling a function to swap the values */
         n.swap(ref a, ref b);

         Console.WriteLine("After swap, value of a : {0}", a);
         Console.WriteLine("After swap, value of b : {0}", b);

         Console.ReadLine();

      }
   }
}

4

tham chiếu và làm việc giống như chuyển qua các tham chiếu và chuyển qua các con trỏ như trong C ++.

Đối với ref, đối số phải khai báo và khởi tạo.

Để ra ngoài, đối số phải được khai báo nhưng có thể hoặc không thể được khởi tạo

        double nbr = 6; // if not initialized we get error
        double dd = doit.square(ref nbr);

        double Half_nbr ; // fine as passed by out, but inside the calling  method you initialize it
        doit.math_routines(nbr, out Half_nbr);

1
Bạn có thể khai báo một biến nội tuyến : out double Half_nbr.
Sebastian Hofmann

4

Thời gian ủy quyền:

(1) Chúng tôi tạo phương thức gọi Main()

(2) nó tạo một đối tượng List (là đối tượng kiểu tham chiếu) và lưu trữ nó trong biến myList.

public sealed class Program 
{
    public static Main() 
    {
        List<int> myList = new List<int>();

Trong thời gian chạy:

(3) Thời gian chạy phân bổ bộ nhớ trên ngăn xếp ở # 00, đủ rộng để lưu địa chỉ (# 00 = myList, vì tên biến thực sự chỉ là bí danh cho các vị trí bộ nhớ)

(4) Thời gian chạy tạo một đối tượng danh sách trên heap tại vị trí bộ nhớ #FF (tất cả các địa chỉ này đều là ví dụ)

(5) Thời gian chạy sau đó sẽ lưu địa chỉ bắt đầu #FF của đối tượng ở # 00 (hoặc bằng từ ngữ, lưu trữ tham chiếu của đối tượng Danh sách trong con trỏ myList)

Quay lại thời gian tác giả:

(6) Sau đó chúng ta truyền đối tượng List làm đối số myParamListcho phương thức được gọi modifyMyListvà gán đối tượng List mới cho nó

List<int> myList = new List<int>();

List<int> newList = ModifyMyList(myList)

public List<int> ModifyMyList(List<int> myParamList){
     myParamList = new List<int>();
     return myParamList;
}

Trong thời gian chạy:

(7) Thời gian chạy bắt đầu thói quen cuộc gọi cho phương thức được gọi và là một phần của nó, kiểm tra loại tham số.

(8) Khi tìm thấy kiểu tham chiếu, nó cấp phát bộ nhớ trên ngăn xếp ở # 04 để đặt bí danh cho biến tham số myParamList.

(9) Sau đó, nó cũng lưu trữ giá trị #FF trong đó.

.

Địa chỉ trong # 00 không bị thay đổi và giữ lại tham chiếu đến #FF (hoặc myListcon trỏ ban đầu không bị xáo trộn).


Các ref từ khóa là một chỉ thị biên dịch bỏ qua thế hệ mã thời gian chạy cho (8) và (9) có nghĩa là sẽ không có phân bổ đống cho các thông số phương pháp. Nó sẽ sử dụng con trỏ # 00 ban đầu để hoạt động trên đối tượng tại #FF. Nếu con trỏ ban đầu không được khởi tạo, bộ thực thi sẽ ngừng phàn nàn rằng nó không thể tiếp tục vì biến không được khởi tạo

Các ra từ khóa là một chỉ thị biên dịch trong đó khá nhiều là giống như ref với một sửa đổi nhỏ ở (9) và (10). Trình biên dịch dự kiến ​​đối số sẽ không được khởi tạo và sẽ tiếp tục với (8), (4) và (5) để tạo một đối tượng trên heap và lưu địa chỉ bắt đầu của nó trong biến đối số. Không có lỗi chưa được khởi tạo sẽ được ném và mọi tham chiếu trước đó được lưu trữ sẽ bị mất.


3

Cũng như cho phép bạn gán lại biến của người khác cho một thể hiện khác của lớp, trả về nhiều giá trị, v.v., sử dụng refhoặc outcho người khác biết bạn cần gì từ họ và bạn dự định làm gì với biến họ cung cấp

  • Bạn không cần ref hoặc outnếu tất cả các bạn đang đi làm là điều Modify bên trong các MyClassví dụ đó là thông qua trong đối số someClass.

    • Phương pháp gọi sẽ thấy những thay đổi như someClass.Message = "Hello World"dù bạn sử dụng ref, outhoặc không có gì
    • Viết someClass = new MyClass()bên trong myFunction(someClass)hoán đổi đối tượng nhìn thấy someClasstrong phạm vi của myFunctionphương thức mà thôi. Phương thức gọi vẫn biết về phiên bản gốc MyClassmà nó đã tạo và truyền cho phương thức của bạn
  • Bạn cần ref hoặc outnếu bạn có kế hoạch hoán đổi someClassmột đối tượng hoàn toàn mới và muốn phương thức gọi để xem sự thay đổi của bạn

    • Viết someClass = new MyClass()bên trong myFunction(out someClass)thay đổi đối tượng nhìn thấy bằng phương thức được gọi làmyFunction

Các lập trình viên khác tồn tại

Và họ muốn biết bạn sẽ làm gì với dữ liệu của họ. Hãy tưởng tượng bạn đang viết một thư viện sẽ được sử dụng bởi hàng triệu nhà phát triển. Bạn muốn họ biết bạn sẽ làm gì với các biến của họ khi họ gọi phương thức của bạn

  • Việc sử dụng reftạo ra một tuyên bố "Truyền một biến được gán cho một giá trị nào đó khi bạn gọi phương thức của tôi. Hãy lưu ý rằng tôi có thể thay đổi nó thành một thứ khác hoàn toàn trong quá trình phương thức của tôi. Đừng hy vọng biến của bạn sẽ trỏ đến đối tượng cũ khi tôi hoàn thành"

  • Việc sử dụng outđưa ra tuyên bố "Truyền biến giữ chỗ cho phương thức của tôi. Không quan trọng nó có giá trị hay không; trình biên dịch sẽ buộc tôi gán nó cho một giá trị mới. Tôi hoàn toàn đảm bảo rằng đối tượng được chỉ bởi biến trước khi bạn gọi phương thức của tôi, sẽ khác khi tôi hoàn thành

Nhân tiện, trong C # 7.2 cũng có một công cụ insửa đổi

Và điều đó ngăn phương thức hoán đổi ví dụ được truyền cho một thể hiện khác. Hãy nghĩ về nó giống như nói với hàng triệu nhà phát triển "hãy chuyển cho tôi tham chiếu biến ban đầu của bạn và tôi hứa sẽ không trao đổi dữ liệu được làm cẩn thận của bạn ra ngoài để lấy thứ khác". incó một số đặc thù và trong một số trường hợp như yêu cầu chuyển đổi ngầm để làm cho khả năng tương thích ngắn của bạn với in inttrình biên dịch sẽ tạm thời tạo một int, mở rộng ngắn của bạn với nó, chuyển nó bằng cách tham chiếu và kết thúc. Nó có thể làm điều này bởi vì bạn đã tuyên bố rằng bạn sẽ không gây rối với nó.


Microsoft đã làm điều này với các .TryParsephương thức trên các loại số:

int i = 98234957;
bool success = int.TryParse("123", out i);

Bằng cách gắn cờ tham số khi outhọ chủ động tuyên bố ở đây "chúng tôi chắc chắn sẽ thay đổi giá trị được tạo ra một cách tỉ mỉ của bạn là 98234957 để lấy thứ khác"

Tất nhiên, họ phải, đối với những thứ như phân tích các loại giá trị bởi vì nếu phương thức phân tích cú pháp không được phép trao đổi loại giá trị cho một thứ khác thì nó sẽ không hoạt động tốt .. Nhưng hãy tưởng tượng có một số phương pháp hư cấu trong một số thư viện bạn đang tạo:

public void PoorlyNamedMethod(out SomeClass x)

Bạn có thể thấy nó là một out, và do đó bạn có thể biết rằng nếu bạn dành hàng giờ đồng hồ để bẻ số, tạo ra một số Kính hoàn hảo:

SomeClass x = SpendHoursMakingMeAPerfectSomeClass();
//now give it to the library
PoorlyNamedMethod(out x);

Chà đó là một sự lãng phí thời gian, dành tất cả những giờ đó để tạo nên lớp học hoàn hảo đó. Nó chắc chắn sẽ bị ném đi và được thay thế bằng PoorlyNamedMethod


3

Đối với những người tìm kiếm câu trả lời súc tích.

Cả hai refouttừ khóa được sử dụng để truyền qua reference.


Một biến reftừ khóa phải có giá trị hoặc phải tham chiếu đến một đối tượng hoặc null trước khi nó đi qua.


Không giống như ref, một biến outtừ khóa phải có giá trị hoặc phải tham chiếu đến một đối tượng hoặc null sau khi vượt qua cũng như không cần phải có giá trị hoặc tham chiếu đến một đối tượng trước khi vượt qua.


2

Để minh họa cho nhiều lời giải thích tuyệt vời, tôi đã phát triển ứng dụng giao diện điều khiển sau:

using System;
using System.Collections.Generic;

namespace CSharpDemos
{
  class Program
  {
    static void Main(string[] args)
    {
      List<string> StringList = new List<string> { "Hello" };
      List<string> StringListRef = new List<string> { "Hallo" };

      AppendWorld(StringList);
      Console.WriteLine(StringList[0] + StringList[1]);

      HalloWelt(ref StringListRef);
      Console.WriteLine(StringListRef[0] + StringListRef[1]);

      CiaoMondo(out List<string> StringListOut);
      Console.WriteLine(StringListOut[0] + StringListOut[1]);
    }

    static void AppendWorld(List<string> LiStri)
    {
      LiStri.Add(" World!");
      LiStri = new List<string> { "¡Hola", " Mundo!" };
      Console.WriteLine(LiStri[0] + LiStri[1]);
    }

    static void HalloWelt(ref List<string> LiStriRef)
     { LiStriRef = new List<string> { LiStriRef[0], " Welt!" }; }

    static void CiaoMondo(out List<string> LiStriOut)
     { LiStriOut = new List<string> { "Ciao", " Mondo!" }; }
   }
}
/*Output:
¡Hola Mundo!
Hello World!
Hallo Welt!
Ciao Mondo!
*/
  • AppendWorld: Một bản sao StringListđược đặt tên LiStriđược thông qua. Khi bắt đầu phương thức, bản sao này tham chiếu danh sách gốc và do đó có thể được sử dụng để sửa đổi danh sách này. Sau đó LiStritham chiếu một List<string>đối tượng khác trong phương thức không ảnh hưởng đến danh sách ban đầu.

  • HalloWelt: LiStriReflà một bí danh của đã được khởi tạo ListStringRef. List<string>Đối tượng thông qua được sử dụng để khởi tạo một đối tượng mới, do đó reflà cần thiết.

  • CiaoMondo: LiStriOutlà bí danh của ListStringOutvà phải được khởi tạo.

Vì vậy, nếu một phương thức chỉ sửa đổi đối tượng được tham chiếu bởi biến đã truyền, trình biên dịch sẽ không cho phép bạn sử dụng outvà bạn không nên sử dụng refvì nó sẽ gây nhầm lẫn không phải trình biên dịch mà là trình đọc mã. Nếu phương thức sẽ làm cho đối số được truyền tham chiếu một đối tượng khác, hãy sử dụng refcho một đối tượng đã được khởi tạo và outcho các phương thức phải khởi tạo một đối tượng mới cho đối số được truyền. Bên cạnh đó, refouthành xử như vậy.


1

Chúng khá giống nhau - sự khác biệt duy nhất là một biến bạn truyền dưới dạng tham số out không cần phải được khởi tạo và phương thức sử dụng tham số ref phải đặt nó thành một biến.

int x;    Foo(out x); // OK 
int y;    Foo(ref y); // Error

Tham số tham chiếu dành cho dữ liệu có thể được sửa đổi, tham số ngoài dành cho dữ liệu là đầu ra bổ sung cho hàm (ví dụ int.TryPude) đã sử dụng giá trị trả về cho một thứ gì đó.


1

Dưới đây tôi đã chỉ ra một ví dụ sử dụng cả Refout . Bây giờ, tất cả các bạn sẽ được xóa về ref và ra.

Trong ví dụ được đề cập dưới đây khi tôi nhận xét // myRefObj = new myClass {Name = "ref bên ngoài được gọi !!"}; dòng, sẽ nhận được một lỗi nói rằng "Việc sử dụng biến cục bộ unassigned 'myRefObj'" , nhưng không có lỗi như vậy trong ra .

Nơi sử dụng Ref : khi chúng ta đang gọi một thủ tục có tham số và tham số tương tự sẽ được sử dụng để lưu trữ đầu ra của Proc đó.

Nơi sử dụng Out: khi chúng ta đang gọi một thủ tục không có tham số và teh cùng param sẽ được sử dụng để trả về giá trị từ Proc đó. Cũng lưu ý đầu ra

public partial class refAndOutUse : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        myClass myRefObj;
        myRefObj = new myClass { Name = "ref outside called!!  <br/>" };
        myRefFunction(ref myRefObj);
        Response.Write(myRefObj.Name); //ref inside function

        myClass myOutObj;
        myOutFunction(out myOutObj);
        Response.Write(myOutObj.Name); //out inside function
    }

    void myRefFunction(ref myClass refObj)
    {
        refObj.Name = "ref inside function <br/>";
        Response.Write(refObj.Name); //ref inside function
    }
    void myOutFunction(out myClass outObj)
    {
        outObj = new myClass { Name = "out inside function <br/>" }; 
        Response.Write(outObj.Name); //out inside function
    }
}

public class myClass
{
    public string Name { get; set; }
} 

1
 public static void Main(string[] args)
    {
        //int a=10;
        //change(ref a);
        //Console.WriteLine(a);
        // Console.Read();

        int b;
        change2(out b);
        Console.WriteLine(b);
        Console.Read();
    }
    // static void change(ref int a)
    //{
    //    a = 20;
    //}

     static void change2(out int b)
     {
         b = 20;
     }

bạn có thể kiểm tra mã này nó sẽ mô tả cho bạn sự khác biệt hoàn toàn của nó khi bạn sử dụng "ref" nghĩa là bạn đã khởi tạo int / chuỗi đó

nhưng khi bạn sử dụng "out" thì nó hoạt động trong cả hai điều kiện khi bạn khởi tạo int / chuỗi đó hay không nhưng bạn phải khởi tạo int / chuỗi đó trong hàm đó


1

Ref: Từ khóa ref được sử dụng để truyền đối số làm tham chiếu. Điều này có nghĩa là khi giá trị của tham số đó được thay đổi trong phương thức, nó sẽ được phản ánh trong phương thức gọi. Một đối số được truyền bằng cách sử dụng từ khóa ref phải được khởi tạo trong phương thức gọi trước khi nó được truyền cho phương thức được gọi.

Out: Từ khóa out cũng được sử dụng để truyền một đối số như từ khóa ref, nhưng đối số có thể được thông qua mà không gán bất kỳ giá trị nào cho nó. Một đối số được truyền bằng cách sử dụng từ khóa out phải được khởi tạo trong phương thức được gọi trước khi nó quay trở lại phương thức gọi.

public class Example
{
 public static void Main() 
 {
 int val1 = 0; //must be initialized 
 int val2; //optional

 Example1(ref val1);
 Console.WriteLine(val1); 

 Example2(out val2);
 Console.WriteLine(val2); 
 }

 static void Example1(ref int value) 
 {
 value = 1;
 }
 static void Example2(out int value) 
 {
 value = 2; 
 }
}

/* Output     1     2     

Tham chiếu và quá tải trong phương thức

Cả ref và out đều không thể được sử dụng trong quá tải phương thức đồng thời. Tuy nhiên, ref và out được xử lý khác nhau trong thời gian chạy nhưng chúng được xử lý giống nhau tại thời gian biên dịch (CLR không phân biệt giữa hai trong khi nó tạo IL cho ref và out).


0

Từ quan điểm của một phương thức nhận một tham số, sự khác biệt giữa refoutlà C # yêu cầu các phương thức đó phải ghi vào mọi outtham số trước khi trả về và không được làm bất cứ điều gì với tham số đó, ngoài việc truyền nó dưới dạng outtham số hoặc ghi vào tham số đó , cho đến khi nó được truyền dưới dạng outtham số cho phương thức khác hoặc được viết trực tiếp. Lưu ý rằng một số ngôn ngữ khác không áp đặt các yêu cầu như vậy; một phương thức ảo hoặc giao diện được khai báo bằng C # với một outtham số có thể được ghi đè bằng ngôn ngữ khác không áp đặt bất kỳ hạn chế đặc biệt nào đối với các tham số đó.

Từ quan điểm của người gọi, trong nhiều trường hợp, C # sẽ giả sử khi gọi một phương thức có outtham số sẽ khiến biến được truyền được ghi mà không được đọc trước. Giả định này có thể không đúng khi gọi các phương thức được viết bằng các ngôn ngữ khác. Ví dụ:

struct MyStruct
{
   ...
   myStruct(IDictionary<int, MyStruct> d)
   {
     d.TryGetValue(23, out this);
   }
}

Nếu myDictionaryxác định một IDictionary<TKey,TValue>triển khai được viết bằng ngôn ngữ khác C #, mặc dù MyStruct s = new MyStruct(myDictionary);trông giống như một bài tập, nó có thể có khả năng skhông được sửa đổi.

Lưu ý rằng các hàm tạo được viết bằng VB.NET, không giống như các hàm trong C #, không đưa ra giả định nào về việc liệu các phương thức được gọi có sửa đổi bất kỳ outtham số nào không và xóa tất cả các trường vô điều kiện. Hành vi kỳ quặc được đề cập ở trên sẽ không xảy ra với mã được viết hoàn toàn bằng VB hoặc hoàn toàn bằng C #, nhưng có thể xảy ra khi mã được viết bằng C # gọi một phương thức được viết bằng VB.NET.


0

Nếu bạn muốn truyền tham số của mình dưới dạng ref thì bạn nên khởi tạo tham số trước khi truyền tham số cho trình biên dịch hàm khác, nó sẽ hiển thị lỗi. Nhưng trong trường hợp hết tham số, bạn không cần khởi tạo tham số đối tượng trước khi chuyển nó sang Phương thức. Bạn có thể khởi tạo đối tượng trong chính phương thức gọi.


-3

Cũng lưu ý rằng tham số tham chiếu được truyền bên trong hàm được làm việc trực tiếp.

Ví dụ,

    public class MyClass
    {
        public string Name { get; set; }
    }

    public void Foo()
    {
        MyClass myObject = new MyClass();
        myObject.Name = "Dog";
        Bar(myObject);
        Console.WriteLine(myObject.Name); // Writes "Dog".
    }

    public void Bar(MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

Điều này sẽ viết Dog, không phải Cat. Do đó bạn nên trực tiếp làm việc trên someObject.


6
Mặc dù mọi thứ ở đây khá đúng nhưng nó không thực sự giải thích sự khác biệt giữa giá trị theo tham chiếu hoặc ngoài. Tốt nhất là một nửa giải thích sự khác biệt giữa các loại tham chiếu và giá trị / bất biến.
Conrad Frix

Nếu bạn muốn mã đó viết mèo, hãy chuyển đối tượng đó cùng với khóa 'ref' như thế này: public static void Bar (ref MyClass someObject), Bar (ref myObject);
Daniel Botero Correa

-4

Tôi có thể không giỏi về điều này, nhưng chắc chắn các chuỗi (mặc dù chúng là các loại tham chiếu kỹ thuật và sống trên heap) được truyền theo giá trị, không phải tham chiếu?

        string a = "Hello";

        string b = "goodbye";

        b = a; //attempt to make b point to a, won't work.

        a = "testing";

        Console.WriteLine(b); //this will produce "hello", NOT "testing"!!!!

Đây là lý do tại sao bạn cần ref nếu bạn muốn các thay đổi tồn tại bên ngoài phạm vi của hàm tạo chúng, bạn sẽ không chuyển tham chiếu khác.

Theo như tôi biết, bạn chỉ cần ref cho các loại cấu trúc / giá trị và chính chuỗi, vì chuỗi là loại tham chiếu giả vờ nhưng không phải là loại giá trị.

Tôi có thể hoàn toàn sai ở đây mặc dù, tôi là người mới.


5
Chào mừng đến với Stack Overflow, Edwin. Các chuỗi được truyền bằng tham chiếu, giống như bất kỳ đối tượng nào khác, theo như tôi biết. Bạn có thể bối rối vì các chuỗi là các đối tượng bất biến, vì vậy không rõ ràng rằng chúng được truyền qua tham chiếu. Hãy tưởng tượng chuỗi đó có một phương thức được gọi là Capitalize()sẽ thay đổi nội dung của chuỗi thành chữ in hoa. Nếu sau đó bạn thay thế dòng của mình a = "testing";bằng a.Capitalize();, thì đầu ra của bạn sẽ là "HELLO", không phải "Xin chào". Một trong những lợi thế của các loại bất biến là bạn có thể chuyển qua các tài liệu tham khảo và không lo lắng về việc mã khác thay đổi giá trị.
Don Kirkby

2
Có ba loại ngữ nghĩa cơ bản mà một loại có thể phơi bày: ngữ nghĩa tham chiếu có thể thay đổi, ngữ nghĩa giá trị có thể thay đổi và ngữ nghĩa bất biến. Xem xét các biến x và y của loại T, có trường hoặc thuộc tính m và giả sử x được sao chép sang y. Nếu T có ngữ nghĩa tham chiếu, các thay đổi thành xm sẽ được quan sát bởi ym Nếu T có ngữ nghĩa giá trị, người ta có thể thay đổi xm mà không ảnh hưởng đến ym Nếu T có ngữ nghĩa bất biến, cả xm và ym sẽ không thay đổi. Ngữ nghĩa bất biến có thể được mô phỏng bởi các đối tượng tham chiếu hoặc giá trị. Chuỗi là đối tượng tham chiếu bất biến.
supercat
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.