ผ่านวัตถุโดยการอ้างอิงหรือค่าใน C #


233

ใน C # ฉันคิดเสมอว่าตัวแปรที่ไม่ใช่ดั้งเดิมถูกส่งผ่านโดยการอ้างอิงและค่าดั้งเดิมที่ส่งผ่านตามค่า

ดังนั้นเมื่อผ่านไปยังวิธีการใด ๆ ที่ไม่ใช่วัตถุดั้งเดิมสิ่งที่ทำกับวัตถุในวิธีการจะมีผลต่อวัตถุที่ถูกส่งผ่าน (สิ่ง C # 101)

อย่างไรก็ตามฉันได้สังเกตเห็นว่าเมื่อฉันส่งวัตถุ System.Drawing.Image ว่าดูเหมือนจะไม่เป็นเช่นนั้น? ถ้าฉันส่งวัตถุ system.drawing.image ไปยังวิธีอื่นแล้วโหลดรูปภาพไปยังวัตถุนั้นให้ปล่อยให้วิธีนั้นพ้นขอบเขตแล้วกลับไปที่วิธีการเรียกใช้อิมเมจนั้นจะไม่ถูกโหลดบนวัตถุดั้งเดิม?

ทำไมนี้


20
ตัวแปรทั้งหมดจะถูกส่งผ่านโดยค่าเริ่มต้นใน C # คุณกำลังส่งค่าของการอ้างอิงในกรณีของประเภทการอ้างอิง
Andrew Barber

คำตอบ:


502

วัตถุไม่ได้ผ่านเลย โดยค่าเริ่มต้นอาร์กิวเมนต์จะถูกประเมินและค่าจะถูกส่งผ่านตามค่าเป็นค่าเริ่มต้นของพารามิเตอร์ของวิธีการที่คุณโทร ตอนนี้จุดสำคัญคือว่าค่าเป็นการอ้างอิงสำหรับประเภทการอ้างอิง - วิธีการไปยังวัตถุ (หรือ null) การเปลี่ยนแปลงวัตถุนั้นจะสามารถมองเห็นได้จากผู้โทร อย่างไรก็ตามการเปลี่ยนค่าของพารามิเตอร์เพื่ออ้างถึงวัตถุอื่นจะไม่สามารถมองเห็นได้เมื่อคุณใช้ pass by value ซึ่งเป็นค่าเริ่มต้นสำหรับทุกประเภท

หากคุณต้องการใช้ Pass-by-Reference คุณต้องใช้outหรือrefไม่ว่าชนิดพารามิเตอร์เป็นประเภทค่าหรือประเภทการอ้างอิง ในกรณีดังกล่าวตัวแปรจะถูกส่งผ่านโดยการอ้างอิงอย่างมีประสิทธิภาพดังนั้นพารามิเตอร์จึงใช้ที่เก็บข้อมูลเดียวกันกับอาร์กิวเมนต์ - และผู้เรียกจะเห็นการเปลี่ยนแปลงพารามิเตอร์

ดังนั้น:

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(...);
}

ผมมีบทความที่จะเข้าสู่รายละเอียดมากขึ้นในเรื่องนี้ โดยพื้นฐานแล้ว "การส่งต่ออ้างอิง" ไม่ได้หมายความว่าคุณคิดว่ามันหมายถึงอะไร


2
คุณถูกฉันไม่เห็นว่า! ฉันกำลังโหลดอิมเมจ = Image.FromFile (.. ) และนั่นคือการแทนที่อิมเมจตัวแปรและไม่เปลี่ยนวัตถุ! :) แน่นอน.
ไมเคิล

1
@Adeem: ไม่มาก - ไม่มี "วัตถุพารามิเตอร์" มีวัตถุที่ค่าพารามิเตอร์หมายถึง ฉันคิดว่าคุณมีความคิดที่ถูกต้อง แต่คำศัพท์มีความสำคัญ :)
Jon Skeet

2
หากเราลบคำหลักrefและoutจาก c # จะเป็นการดีหรือไม่ที่จะบอกว่า c # ส่งผ่านพารามิเตอร์แบบเดียวกับที่ java ทำเช่นนั้นด้วยค่าเสมอ มีความแตกต่างกับจาวาหรือไม่
บรอดแบนด์

1
@broadband: ใช่โหมดการส่งผ่านเริ่มต้นคือค่า แม้ว่าแน่นอน C # มีพอยน์เตอร์และประเภทค่าที่กำหนดเองซึ่งทำให้ซับซ้อนกว่าใน Java เล็กน้อย
Jon Skeet

3
@Vippy: ไม่ไม่เลย มันเป็นสำเนาของการอ้างอิง ฉันขอแนะนำให้คุณอ่านบทความที่เชื่อมโยง
Jon Skeet

18

ตัวอย่างโค้ดอีกหนึ่งตัวอย่างเพื่อแสดงสิ่งนี้:

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

และผลลัพธ์:

TestPlain: 0

TestRef: 5

TestObjPlain: การทดสอบ

TestObjRef: TestObjRef


2
ดังนั้นประเภทการอ้างอิงโดยทั่วไปยังคงต้องผ่านการอ้างอิงหากเราต้องการเห็นการเปลี่ยนแปลงในฟังก์ชั่นการโทร
แตกหัก

1
สตริงเป็นประเภทการอ้างอิงที่ไม่เปลี่ยนรูป วิธีที่ไม่เปลี่ยนรูปแบบจะไม่สามารถเปลี่ยนแปลงได้หลังจากสร้างขึ้นแล้ว การเปลี่ยนแปลงสตริงจะสร้างสตริงใหม่ นั่นเป็นเหตุผลที่จำเป็นต้องมีการส่งผ่านสตริงเป็น 'ref' เพื่อรับการเปลี่ยนแปลงในวิธีการโทร วัตถุอื่น (เช่นพนักงาน) สามารถส่งผ่านโดยไม่ต้อง 'อ้างอิง' เพื่อรับการเปลี่ยนแปลงกลับมาในวิธีการโทร
หิมาลัย Garg

1
@vmg ตาม HimalayaGarg นี่ไม่ใช่ตัวอย่างที่ดีมาก คุณต้องรวมตัวอย่างประเภทการอ้างอิงอื่นที่ไม่เปลี่ยนรูป
แดเนียล

11

เพิ่มคำตอบที่ดีจำนวนมากแล้ว ฉันยังต้องการมีส่วนร่วมอาจเป็นไปได้ที่จะชี้แจงเพิ่มเติมเล็กน้อย

เมื่อคุณส่งผ่านอินสแตนซ์เป็นอาร์กิวเมนต์ไปยังเมธอดมันจะส่งผ่านcopyอินสแตนซ์ของ ทีนี้หากอินสแตนซ์ที่คุณผ่านคือvalue type(อยู่ในstack) คุณจะส่งสำเนาของค่านั้นดังนั้นหากคุณแก้ไขมันจะไม่ปรากฏในผู้โทร หากอินสแตนซ์เป็นประเภทการอ้างอิงคุณจะต้องส่งสำเนาของการอ้างอิง (อยู่ในstack) อีกครั้งไปยังวัตถุ คุณได้การอ้างอิงสองครั้งไปยังวัตถุเดียวกัน ทั้งคู่สามารถแก้ไขวัตถุได้ แต่ถ้าภายในเมธอดเนื้อหาคุณสร้างอินสแตนซ์วัตถุใหม่สำเนาของการอ้างอิงของคุณจะไม่อ้างถึงวัตถุดั้งเดิมอีกต่อไปมันจะอ้างถึงวัตถุใหม่ที่คุณเพิ่งสร้างขึ้น ดังนั้นคุณจะมี 2 ข้อมูลอ้างอิงและวัตถุ 2 รายการ


นี่ควรเป็นคำตอบที่เลือก!
ม.ค.

ฉันเห็นด้วยอย่างสมบูรณ์! :)
JOSEFtw

8

ฉันเดาว่ามันชัดเจนขึ้นเมื่อคุณทำเช่นนี้ ฉันแนะนำให้ดาวน์โหลดLinqPadเพื่อทดสอบสิ่งนี้

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

และนั่นควรจะเป็นผลลัพธ์

WontUpdate

ชื่อ: Egli, นามสกุล: Becerra

UpdateImplicitly

ชื่อ: Favio, นามสกุล: Becerra

UpdateExplicitly

ชื่อ: Favio, นามสกุล: Becerra


และสิ่งที่เกี่ยวกับโมฆะสาธารณะคงที่ WhatAbout (Person p) {p = new Person () {FirstName = "First", LastName = "Last"}; }. :)
Marin Popov

4

เมื่อคุณส่งSystem.Drawing.Imageวัตถุชนิดไปยังวิธีที่คุณจริง ๆ แล้วผ่านสำเนาของการอ้างอิงไปยังวัตถุนั้น

ดังนั้นหากในวิธีนั้นคุณกำลังโหลดภาพใหม่คุณกำลังโหลดโดยใช้การอ้างอิงใหม่ / คัดลอก คุณไม่ได้ทำการเปลี่ยนแปลงในแบบดั้งเดิม

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
}

2

คุณผ่านวัตถุไปยังวิธีการได้อย่างไร

คุณกำลังทำสิ่งใหม่ภายในวิธีการนั้นสำหรับวัตถุหรือไม่ ถ้าเป็นเช่นนั้นคุณต้องใช้refวิธีการ

ลิงค์ต่อไปนี้ให้ความคิดที่ดีขึ้น

http://dotnetstep.blogspot.com/2008/09/passing-reference-type-byval-or-byref.html


-1

ในการอ้างอิงผ่านคุณเพิ่ม "อ้างอิง" ในพารามิเตอร์ฟังก์ชันและอีกอย่างหนึ่งที่คุณควรจะประกาศฟังก์ชั่น "คงที่" เพราะหลักคือคงที่ (# 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);  
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.