Truyền đối tượng theo tham chiếu hoặc giá trị trong C #


233

Trong C #, tôi luôn nghĩ rằng các biến không nguyên thủy được truyền bởi các giá trị tham chiếu và nguyên thủy được truyền theo giá trị.

Vì vậy, khi truyền vào một phương thức bất kỳ đối tượng không nguyên thủy nào, bất kỳ điều gì được thực hiện cho đối tượng trong phương thức sẽ ảnh hưởng đến đối tượng được truyền. (Công cụ C # 101)

Tuy nhiên, tôi đã nhận thấy rằng khi tôi vượt qua một đối tượng System.Drawing.Image, điều này dường như không phải là trường hợp? Nếu tôi chuyển một đối tượng system.drawing.image sang một phương thức khác và tải một hình ảnh lên đối tượng đó, thì hãy để phương thức đó ra khỏi phạm vi và quay lại phương thức gọi, hình ảnh đó không được tải trên đối tượng ban đầu?

Tại sao lại thế này?


20
Tất cả các biến được truyền theo giá trị theo mặc định trong C #. Bạn đang chuyển giá trị của tham chiếu trong trường hợp các loại tham chiếu.
Andrew Barber

Câu trả lời:


502

Đối tượng không được thông qua cả. Theo mặc định, đối số được ước tính và giá trị của nó được truyền, theo giá trị, là giá trị ban đầu của tham số của phương thức bạn đang gọi. Bây giờ điểm quan trọng là giá trị là một tham chiếu cho các loại tham chiếu - một cách để đến một đối tượng (hoặc null). Thay đổi đối tượng đó sẽ được hiển thị từ người gọi. Tuy nhiên, việc thay đổi giá trị của tham số để tham chiếu đến một đối tượng khác sẽ không hiển thị khi bạn đang sử dụng pass by value, đây là mặc định cho tất cả các loại.

Nếu bạn muốn sử dụng tham chiếu qua, bạn phải sử dụng outhoặc ref, cho dù loại tham số là loại giá trị hay loại tham chiếu. Trong trường hợp đó, chính biến đó được truyền bằng tham chiếu, vì vậy tham số sử dụng cùng một vị trí lưu trữ làm đối số - và các thay đổi đối với chính tham số được người gọi nhìn thấy.

Vì thế:

public void Foo(Image image)
{
    // This change won't be seen by the caller: it's changing the value
    // of the parameter.
    image = Image.FromStream(...);
}

public void Foo(ref Image image)
{
    // This change *will* be seen by the caller: it's changing the value
    // of the parameter, but we're using pass by reference
    image = Image.FromStream(...);
}

public void Foo(Image image)
{
    // This change *will* be seen by the caller: it's changing the data
    // within the object that the parameter value refers to.
    image.RotateFlip(...);
}

Tôi có một bài viết đi sâu vào chi tiết hơn rất nhiều về vấn đề này . Về cơ bản, "vượt qua tham chiếu" không có nghĩa là bạn nghĩ nó có nghĩa gì.


2
Quyền của bạn, tôi đã không nhìn thấy điều đó! Tôi đang tải image = Image.FromFile (..) và điều đó đã thay thế hình ảnh biến đổi và không thay đổi đối tượng! :) tất nhiên.
michael

1
@Adeem: Không hoàn toàn - không có "đối tượng tham số", có đối tượng mà giá trị của tham số đề cập đến. Tôi nghĩ rằng bạn đã có ý tưởng đúng, nhưng vấn đề thuật ngữ :)
Jon Skeet

2
Nếu chúng ta bỏ các từ khóa refouttừ c #, có thể nói rằng c # truyền các tham số giống như java không, nghĩa là luôn luôn theo giá trị. Có sự khác biệt nào với java không.
băng thông rộng

1
@broadband: Có, chế độ chuyển mặc định là theo giá trị. Mặc dù tất nhiên C # có các con trỏ và các loại giá trị tùy chỉnh, điều này làm cho tất cả phức tạp hơn một chút so với trong Java.
Jon Skeet

3
@Vippy: Không, hoàn toàn không. Đó là một bản sao của tài liệu tham khảo . Tôi đề nghị bạn đọc bài viết liên kết.
Jon Skeet

18

Thêm một mẫu mã để giới thiệu điều này:

void Main()
{


    int k = 0;
    TestPlain(k);
    Console.WriteLine("TestPlain:" + k);

    TestRef(ref k);
    Console.WriteLine("TestRef:" + k);

    string t = "test";

    TestObjPlain(t);
    Console.WriteLine("TestObjPlain:" +t);

    TestObjRef(ref t);
    Console.WriteLine("TestObjRef:" + t);
}

public static void TestPlain(int i)
{
    i = 5;
}

public static void TestRef(ref int i)
{
    i = 5;
}

public static void TestObjPlain(string s)
{
    s = "TestObjPlain";
}

public static void TestObjRef(ref string s)
{
    s = "TestObjRef";
}

Và đầu ra:

Kiểm tra chính xác: 0

TestRef: 5

TestObjPlain: kiểm tra

TestObjRef: TestObjRef


2
Vì vậy, về cơ bản loại tham chiếu vẫn CẦN ĐƯỢC THAM GIA làm tham chiếu nếu chúng ta muốn xem các thay đổi trong chức năng Người gọi.
Không thể phá vỡ

1
Chuỗi là loại tham chiếu bất biến. Phương tiện bất biến, nó không thể thay đổi sau khi nó được tạo ra. Mỗi thay đổi đối với một chuỗi sẽ tạo ra một chuỗi mới. Đó là lý do tại sao các chuỗi cần phải được chuyển thành 'ref' để có được sự thay đổi trong phương thức gọi. Các đối tượng khác (ví dụ nhân viên) có thể được thông qua mà không có 'ref' để lấy lại các thay đổi trong phương thức gọi.
Himalaya Garg

1
@vmg, theo HimalayaGarg, đây không phải là một ví dụ hay. Bạn cần bao gồm một ví dụ loại tham chiếu khác không phải là bất biến.
Daniel

11

Rất nhiều câu trả lời hay đã được thêm vào. Tôi vẫn muốn đóng góp, có thể nó sẽ làm rõ hơn một chút.

Khi bạn truyền một thể hiện làm đối số cho phương thức, nó sẽ truyền copyđối tượng đó. Bây giờ, nếu trường hợp bạn vượt qua là một value type(nằm trong stack) bạn chuyển bản sao của giá trị đó, vì vậy nếu bạn sửa đổi nó, nó sẽ không được phản ánh trong người gọi. Nếu thể hiện là một kiểu tham chiếu, bạn chuyển bản sao của tham chiếu (một lần nữa nằm trong stack) cho đối tượng. Vì vậy, bạn có hai tài liệu tham khảo cho cùng một đối tượng. Cả hai đều có thể sửa đổi đối tượng. Nhưng nếu trong thân phương thức, bạn khởi tạo đối tượng mới, bản sao tham chiếu của bạn sẽ không còn tham chiếu đến đối tượng ban đầu, nó sẽ tham chiếu đến đối tượng mới mà bạn vừa tạo. Vì vậy, cuối cùng bạn sẽ có 2 tài liệu tham khảo và 2 đối tượng.


Đây nên là câu trả lời được lựa chọn!
JAN

Tôi hoàn toàn đồng ý! :)
JOSEFtw

8

Tôi đoán nó rõ ràng hơn khi bạn làm như thế này. Tôi khuyên bạn nên tải LinqPad để kiểm tra những thứ như thế này.

void Main()
{
    var Person = new Person(){FirstName = "Egli", LastName = "Becerra"};

    //Will update egli
    WontUpdate(Person);
    Console.WriteLine("WontUpdate");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");

    UpdateImplicitly(Person);
    Console.WriteLine("UpdateImplicitly");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");

    UpdateExplicitly(ref Person);
    Console.WriteLine("UpdateExplicitly");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");
}

//Class to test
public class Person{
    public string FirstName {get; set;}
    public string LastName {get; set;}

    public string printName(){
        return $"First name: {FirstName} Last name:{LastName}";
    }
}

public static void WontUpdate(Person p)
{
    //New instance does jack...
    var newP = new Person(){FirstName = p.FirstName, LastName = p.LastName};
    newP.FirstName = "Favio";
    newP.LastName = "Becerra";
}

public static void UpdateImplicitly(Person p)
{
    //Passing by reference implicitly
    p.FirstName = "Favio";
    p.LastName = "Becerra";
}

public static void UpdateExplicitly(ref Person p)
{
    //Again passing by reference explicitly (reduntant)
    p.FirstName = "Favio";
    p.LastName = "Becerra";
}

Và nên xuất ra

Không cập nhật

Tên: Egli, Họ: Becerra

Cập nhật rõ ràng

Tên: Favio, Họ: Becerra

Cập nhật

Tên: Favio, Họ: Becerra


và những gì về void static void What About (Person p) {p = new Person () {FirstName = "First", LastName = "Last"}; }. :)
Marin Popov

4

Khi bạn truyền System.Drawing.Imageđối tượng kiểu cho một phương thức, bạn thực sự đang truyền một bản sao tham chiếu đến đối tượng đó.

Vì vậy, nếu bên trong phương thức đó bạn đang tải một hình ảnh mới mà bạn đang tải bằng cách sử dụng tham chiếu mới / sao chép. Bạn không tạo ra sự thay đổi trong bản gốc.

YourMethod(System.Drawing.Image image)
{
    //now this image is a new reference
    //if you load a new image 
    image = new Image()..
    //you are not changing the original reference you are just changing the copy of original reference
}


-1

Trong Pass By Reference Bạn chỉ thêm "ref" trong các tham số chức năng và một điều nữa bạn nên khai báo hàm "static" vì main là static (# public void main(String[] args))!

namespace preparation
{
  public  class Program
    {
      public static void swap(ref int lhs,ref int rhs)
      {
          int temp = lhs;
          lhs = rhs;
          rhs = temp;
      }
          static void Main(string[] args)
        {
            int a = 10;
            int b = 80;

  Console.WriteLine("a is before sort " + a);
            Console.WriteLine("b is before sort " + b);
            swap(ref a, ref b);
            Console.WriteLine("");
            Console.WriteLine("a is after sort " + a);
            Console.WriteLine("b is after sort " + b);  
        }
    }
}
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.