การใช้ "อ้างอิง" สำหรับตัวแปรประเภทการอ้างอิงใน C # คืออะไร?


176

ฉันเข้าใจว่าถ้าฉันผ่านค่าประเภท ( int,, structฯลฯ ) เป็นพารามิเตอร์ (โดยไม่มีrefคำหลัก) สำเนาของตัวแปรนั้นจะถูกส่งผ่านไปยังวิธีการ แต่ถ้าฉันใช้refคำหลักการอ้างอิงถึงตัวแปรนั้นจะถูกส่งผ่าน ไม่ใช่ใหม่

แต่ด้วยประเภทการอ้างอิงเช่นคลาสแม้ไม่มีrefคำหลักการอ้างอิงจะถูกส่งผ่านไปยังเมธอดไม่ใช่การคัดลอก ดังนั้นการใช้refคำหลักที่มีประเภทอ้างอิงคืออะไร?


ยกตัวอย่างเช่น

var x = new Foo();

ข้อแตกต่างระหว่างสิ่งต่อไปนี้คืออะไร?

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

และ

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

คำตอบ:


154

คุณสามารถเปลี่ยนสิ่งที่fooจะใช้คะแนนy:

Foo foo = new Foo("1");

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

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

17
เพื่อให้คุณโดยทั่วไปได้รับการอ้างอิงไปที่เดิมอ้างอิง
lhahne

2
คุณสามารถเปลี่ยนสิ่งที่อ้างอิงดั้งเดิมหมายถึงดังนั้นใช่
user7116

1
คริสคำอธิบายของคุณยอดเยี่ยมมาก ขอบคุณที่ช่วยฉันเข้าใจแนวคิดนี้
Andreas Grech

4
ดังนั้นการใช้ 'อ้างอิง' บนวัตถุก็เหมือนกับการใช้ตัวชี้สองเท่าใน C ++?
Tom Hazel

1
@TomHazel: -ishหากคุณใช้พอยน์เตอร์ "double" ใน C ++ เพื่อเปลี่ยนสิ่งที่ตัวชี้ชี้ไป
user7116

29

มีหลายกรณีที่คุณต้องการแก้ไขการอ้างอิงจริงไม่ใช่วัตถุที่ชี้ไปที่:

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 เขียนบทความที่ยอดเยี่ยมเกี่ยวกับการส่งพารามิเตอร์ใน C # มันมีรายละเอียดอย่างชัดเจนพฤติกรรมที่แน่นอนและการใช้งานของพารามิเตอร์การส่งผ่านตามค่าโดยการอ้างอิง ( ref) และโดยการส่งออก ( out)

ต่อไปนี้เป็นข้อความอ้างอิงที่สำคัญจากหน้าเว็บที่เกี่ยวข้องกับrefพารามิเตอร์:

พารามิเตอร์อ้างอิงไม่ส่งค่าของตัวแปรที่ใช้ในการเรียกใช้ฟังก์ชันสมาชิก - พวกมันใช้ตัวแปรเอง แทนที่จะสร้างตำแหน่งหน่วยเก็บข้อมูลใหม่สำหรับตัวแปรในการประกาศสมาชิกฟังก์ชันตำแหน่งหน่วยเก็บข้อมูลเดียวกันจะถูกใช้ดังนั้นค่าของตัวแปรในสมาชิกฟังก์ชันและค่าของพารามิเตอร์อ้างอิงจะเหมือนกันเสมอ พารามิเตอร์อ้างอิงจำเป็นต้องมีตัวดัดแปลง ref เป็นส่วนหนึ่งของทั้งการประกาศและการเรียก - ซึ่งหมายความว่ามันชัดเจนเสมอเมื่อคุณส่งบางอย่างโดยการอ้างอิง


11
ผมชอบความคล้ายคลึงกันของสุนัขที่ผ่านการข่มของคุณให้เพื่อนผ่านการอ้างอิงโดยค่า ... มันหยุดลงได้อย่างรวดเร็ว แต่เพราะฉันคิดว่าคุณจะอาจจะแจ้งให้ทราบล่วงหน้าหากเพื่อนของคุณซื้อขายขึ้น Shitzu ของคุณเพื่อ Doberman ก่อนที่เขาจะส่งคุณกลับ the leash ;-)
corlettk

16

อธิบายได้อย่างดีมากที่นี่: http://msdn.microsoft.com/en-us/library/s6938f28.aspx

บทคัดย่อจากบทความ:

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


4
คำอธิบายนั้นดีมากจริงๆ อย่างไรก็ตามคำตอบสำหรับลิงก์อย่างเดียวนั้นไม่ได้รับการสนับสนุนบน SO ฉันเพิ่มบทสรุปจากบทความเพื่อความสะดวกแก่ผู้อ่านที่นี่
Marcel

10

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

C # มีคีย์เวิร์ด 'out' ซึ่งมีจำนวนมากเช่น ref ยกเว้นว่าด้วย 'ref' อาร์กิวเมนต์จะต้องเริ่มต้นก่อนที่จะเรียกเมธอดและด้วย 'out' คุณต้องกำหนดค่าในวิธีการรับ


5

ช่วยให้คุณสามารถแก้ไขการอ้างอิงที่ส่งผ่านมาเช่น

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

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

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

นอกจากนี้คุณยังสามารถใช้ออกถ้าคุณไม่สนใจเกี่ยวกับการอ้างอิงผ่าน:

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

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

4

รหัสอีกมัด

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

นอกเหนือจากคำตอบที่มีอยู่:

เมื่อคุณถามถึงความแตกต่างของ 2 วิธี: ไม่มีความแปรปรวนร่วม (ntra) เมื่อใช้refหรือ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

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

ชนิดของค่า: ตัวบ่งชี้ (มีค่า = ที่อยู่ของค่าสแต็ก) ----> ค่าของชนิดค่า

ประเภทการอ้างอิง: ตัวระบุ (มีค่า = ที่อยู่ของมูลค่าสแต็ก) ----> (มีค่า = ที่อยู่ของมูลค่าฮีป) ----> ค่าฮีป (ส่วนใหญ่มักจะมีที่อยู่กับค่าอื่น ๆ ) ลองจินตนาการถึงลูกศร เส้นทางไปยัง Array [0], Array [1], Array [2]

วิธีเดียวในการเปลี่ยนค่าคือติดตามลูกศร หากลูกศรหนึ่งหลงทาง / เปลี่ยนค่าไม่สามารถเข้าถึงได้


-1

ตัวแปรอ้างอิงมีที่อยู่จากที่หนึ่งไปยังอีกที่หนึ่งดังนั้นการปรับปรุงใด ๆ ในสถานที่ใด ๆ จะสะท้อนให้เห็นถึงสถานที่ทั้งหมดจากนั้นการใช้ REF คืออะไร ตัวแปรอ้างอิง (405) ดีจนกระทั่งไม่มีหน่วยความจำใหม่ถูกจัดสรรให้กับตัวแปรอ้างอิงที่ส่งผ่านในวิธีการ

เมื่อหน่วยความจำใหม่จัดสรร (410) ดังนั้นการเปลี่ยนแปลงค่าบนวัตถุนี้ (408) จะไม่สะท้อนทุกที่ สำหรับการอ้างอิงนี้มา Ref คือการอ้างอิงของการอ้างอิงดังนั้นเมื่อใดก็ตามที่หน่วยความจำใหม่ได้รับการจัดสรรเพราะมันชี้ไปที่ตำแหน่งนั้นดังนั้นค่าสามารถแชร์ได้ทุก ๆ หนึ่ง คุณสามารถดูภาพเพื่อความชัดเจนมากขึ้น

อ้างอิงในตัวแปรอ้างอิง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.