ความแตกต่างระหว่างคำหลัก 'อ้างอิง' และ 'ออก' คืออะไร


891

ฉันกำลังสร้างฟังก์ชั่นที่ฉันต้องผ่านวัตถุเพื่อให้สามารถแก้ไขได้โดยฟังก์ชั่น อะไรคือความแตกต่างระหว่าง:

public void myFunction(ref MyClass someClass)

และ

public void myFunction(out MyClass someClass)

ฉันควรใช้แบบใดและเพราะเหตุใด


69
คุณ: ฉันต้องผ่านวัตถุเพื่อให้สามารถแก้ไขได้ดูเหมือนว่าMyClassจะเป็นclassประเภทเช่นประเภทอ้างอิง ในกรณีดังกล่าววัตถุที่คุณผ่านสามารถแก้ไขmyFunctionได้ด้วยการไม่มีคำหลักref/ จะได้รับการอ้างอิงใหม่ที่ชี้ไปที่วัตถุเดียวกันและสามารถแก้ไขวัตถุเดียวกันได้มากเท่าที่ต้องการ ความแตกต่างของคำหลักที่จะทำให้จะได้รับที่ได้รับเดียวกันอ้างอิงถึงวัตถุเดียวกัน นั่นจะมีความสำคัญต่อเมื่อต้องเปลี่ยนการอ้างอิงให้ชี้ไปที่วัตถุอื่น outmyFunctionrefmyFunctionmyFunction
Jeppe Stig Nielsen

3
ฉันงงงวยกับคำตอบที่สับสนที่นี่เมื่อ @ AnthonyKolesov's ค่อนข้างสมบูรณ์แบบ
o0 '

การประกาศเมธอด out มีประโยชน์เมื่อคุณต้องการให้เมธอดส่งคืนค่าหลายค่า สามารถกำหนดหนึ่งอาร์กิวเมนต์ให้เป็นค่าว่างได้ วิธีนี้ช่วยให้วิธีการส่งกลับค่าเป็นทางเลือก
Yevgraf Andreyevich Zhivago

คำอธิบายที่นี่พร้อมตัวอย่างสามารถเข้าใจได้มากขึ้น :) dotnet-tricks.com/Tutorial/csharp/ ......
Prageeth godage

2
@ JeppeStigNielsen ความคิดเห็นคือในทางเทคนิคคำตอบที่ถูกต้อง (เท่านั้น) สำหรับคำถามที่แท้จริงของ OP ในการส่งวัตถุไปยังวิธีการเพื่อให้วิธีการนั้นสามารถปรับเปลี่ยนวัตถุได้เพียงแค่ส่งวัตถุ (อ้างอิงถึง) ไปยังวิธีการตามค่า การเปลี่ยนวัตถุภายในวิธีการผ่านการโต้แย้งวัตถุปรับเปลี่ยนวัตถุเดิมแม้ว่าวิธีการที่มีตัวแปรแยกของตัวเอง (ซึ่งอ้างอิงถึงวัตถุเดียวกัน)
David R Tribble

คำตอบ:


1160

refบอกคอมไพเลอร์ว่าวัตถุถูกเริ่มต้นก่อนที่จะเข้าฟังก์ชั่นในขณะที่outบอกคอมไพเลอร์ว่าวัตถุจะเริ่มต้นได้ภายในฟังก์ชั่น

ดังนั้นในขณะที่refเป็นสองวิธีoutคือออกเท่านั้น


270
สิ่งที่ยอดเยี่ยมอีกอย่างที่เฉพาะเจาะจงสำหรับการออกคือฟังก์ชั่นมีการกำหนดให้กับพารามิเตอร์ออก ไม่อนุญาตให้ปล่อยทิ้งไว้โดยไม่ได้กำหนด
Daniel Earwicker

7
อ้างอิงถึงค่าประเภทใด เนื่องจากประเภทการอ้างอิงจะผ่านไปโดยอ้างอิงเสมอ
เกิดความผิดพลาด

3
ใช่. ชนิดของมูลค่ารวมถึง structs
Rune Grimstad

17
@faulty: ไม่การอ้างอิงไม่สามารถใช้ได้กับประเภทค่าเท่านั้น อ้างอิง / ออกเหมือนตัวชี้ใน C / C ++ พวกเขาจัดการกับตำแหน่งหน่วยความจำของวัตถุ (ทางอ้อมใน C #) แทนวัตถุโดยตรง
ธ.ค.

52
@faulty: ตอบโต้ประเภทการอ้างอิงจะถูกส่งผ่านตามค่าใน C # เสมอเว้นแต่คุณจะใช้ตัวระบุการอ้างอิง หากคุณตั้งค่า myval = somenewval เอฟเฟกต์จะอยู่ในขอบเขตของฟังก์ชันนั้นเท่านั้น คำหลักอ้างอิงจะช่วยให้คุณเปลี่ยน myval ให้ชี้ไปที่ somenewval
JasonTrue

535

refวิธีการปรับปรุงที่:

  1. ค่าที่ตั้งไว้แล้วและ
  2. วิธีนี้สามารถอ่านและแก้ไขได้

outวิธีการปรับปรุงที่:

  1. ค่าไม่ได้ถูกตั้งค่าและไม่สามารถอ่านได้โดยวิธีการจนกว่าจะมีการตั้งค่า
  2. วิธีการจะต้องตั้งค่าก่อนที่จะกลับมา

30
คำตอบนี้ชัดเจนและรัดกุมที่สุดจะอธิบายข้อ จำกัด ที่คอมไพเลอร์เรียกเก็บเมื่อใช้คำหลักออกซึ่งตรงข้ามกับคำหลักอ้างอิง
ลูกศิษย์ของ Dr. Wily

5
จาก MSDN: พารามิเตอร์ ref จะต้องเริ่มต้นก่อนการใช้งานในขณะที่พารามิเตอร์ out ไม่จำเป็นต้องเริ่มต้นอย่างชัดเจนก่อนที่จะส่งผ่านและค่าก่อนหน้านี้จะถูกละเว้น
Shiva Kumar

1
ด้วยoutสามารถอ่านได้ทั้งหมดในวิธีการก่อนที่จะถูกกำหนดโดยวิธีการนั้นถ้ามันได้รับการเริ่มต้นก่อนที่วิธีการที่ถูกเรียก? ฉันหมายถึงวิธีการที่เรียกสามารถอ่านสิ่งที่วิธีการโทรผ่านไปเป็นอาร์กิวเมนต์?
Panzercrisis

3
Panzercrisis สำหรับ "out" เมธอดที่เรียกสามารถอ่านได้หากตั้งค่าไว้แล้ว แต่จะต้องตั้งค่าอีกครั้ง
robert jebakumar2

146

สมมติว่า Dom ปรากฏตัวที่ห้องเล็ก ๆ ของ Peter เกี่ยวกับบันทึกช่วยจำเกี่ยวกับรายงาน TPS

ถ้า Dom เป็นอาร์กิวเมนต์ ref เขาจะมีสำเนาบันทึกที่พิมพ์ออกมา

ถ้าดอมเป็นข้อโต้แย้งเขาจะทำให้ปีเตอร์พิมพ์สำเนาบันทึกใหม่เพื่อให้เขานำติดตัวไปด้วย


54
ref Dom จะเขียนรายงานด้วยดินสอเพื่อให้ Peter สามารถแก้ไขได้
Deebster

6
@ คุณเห็นด้วยหรือเปล่าอุปมาที่ไม่เคยทำอะไรกับคุณทำไมคุณต้องทรมานมันด้วย? ;)
Michael Blackburn

21
stackoverflow ต้องการโพสต์เช่นนี้มากขึ้น
Frank Visaggio

2
ในกรณีที่ใครบางคนพบคำตอบนี้เพียงครึ่งเดียวโปรดดูภาพยนตร์ "Office Space"
displayName

และเจ้านายและ Dom and Peters จะยืนอยู่ใน Dom (เป็นข้อโต้แย้ง) บังคับให้ทั้งคู่ทำงานในการพิมพ์ออกมาอีกครั้งจนกระทั่งปีเตอร์ส่ง Domd ออกไป
Patrick Artner

57

ฉันจะลองทำตามคำอธิบาย:

ฉันคิดว่าเราเข้าใจว่าประเภทค่าทำงานอย่างไร ประเภทค่าคือ (int, long, struct ฯลฯ ) เมื่อคุณส่งพวกเขาในการทำงานโดยไม่มีคำสั่งเตะมันสำเนาข้อมูล ทุกสิ่งที่คุณทำกับข้อมูลนั้นในฟังก์ชั่นจะมีผลเฉพาะกับการคัดลอกไม่ใช่ต้นฉบับ คำสั่ง ref ส่งข้อมูลจริงและการเปลี่ยนแปลงใด ๆ จะส่งผลต่อข้อมูลภายนอกฟังก์ชั่น

ตกลงไปยังส่วนที่สับสนประเภทการอ้างอิง:

ให้สร้างประเภทการอ้างอิง:

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

เมื่อคุณใหม่บางอย่างโครงการสองส่วนจะถูกสร้างขึ้น:

  1. บล็อกของหน่วยความจำที่เก็บข้อมูลสำหรับบางอย่าง
  2. การอ้างอิง (ตัวชี้) ไปยังบล็อกของข้อมูลนั้น

ตอนนี้เมื่อคุณส่งบางอย่างลงในเมธอดโดยไม่มีการอ้างอิงให้คัดลอกตัวชี้อ้างอิงไม่ใช่ข้อมูล ดังนั้นตอนนี้คุณมีสิ่งนี้:

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

การอ้างอิงสองรายการชี้ไปที่วัตถุเดียวกัน หากคุณปรับเปลี่ยนคุณสมบัติในบางวัตถุโดยใช้การอ้างอิง 2 มันจะส่งผลกระทบต่อข้อมูลเดียวกันที่ชี้ไปตามการอ้างอิง 1

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

หากคุณไม่มีการอ้างอิง 2 หรือชี้ไปที่ข้อมูลใหม่มันจะไม่ส่งผลกระทบต่อการอ้างอิง 1 หรือข้อมูลการอ้างอิง 1 ชี้ไปที่

(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

ตอนนี้จะเกิดอะไรขึ้นเมื่อคุณส่งวัตถุโดยอ้างอิงถึงวิธี? การอ้างอิงจริงกับsomeobjectจะถูกส่งไปยังเมธอด ดังนั้นตอนนี้คุณมีข้อมูลอ้างอิงเดียวเท่านั้น:

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

แต่สิ่งนี้หมายความว่าอย่างไร มันทำหน้าที่เหมือนกันกับการส่งวัตถุโดยไม่อ้างอิงยกเว้นสองสิ่งหลัก:

1) เมื่อคุณโมฆะการอ้างอิงภายในวิธีการมันจะเป็นโมฆะการอ้างอิงนอกวิธีการ

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

2) ตอนนี้คุณสามารถชี้การอ้างอิงไปยังตำแหน่งข้อมูลที่แตกต่างอย่างสิ้นเชิงและการอ้างอิงภายนอกฟังก์ชั่นจะชี้ไปที่ตำแหน่งข้อมูลใหม่

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

คุณหมายถึงหลังจากทั้งหมด (ในกรณีอ้างอิง) มีเพียงหนึ่งการอ้างอิงไปยังข้อมูล แต่มีสองชื่อแทนมัน ขวา?
Sadiq

3
โหวตขึ้นสำหรับคำอธิบายที่ชัดเจน แต่ฉันคิดว่าสิ่งนี้จะไม่ตอบคำถามเนื่องจากไม่ได้อธิบายความแตกต่างระหว่างrefและoutพารามิเตอร์
จอยซ์บาบู

1
น่าอัศจรรย์ คุณสามารถอธิบายเช่นเดียวกับoutคำหลักได้หรือไม่
Asif Mushtaq

28

เตะที่อยู่ในและออก

คุณควรใช้outตามความต้องการของคุณ


ไม่ค่อนข้างเป็นคำตอบที่ได้รับการยอมรับถ้าทิศทางและไร้ประโยชน์ละเว้นประเภทค่าถ้าไม่ถูกส่งกลับ
kenny

@ เคนนี่: คุณช่วยอธิบายให้ชัดเจนหน่อยได้ไหม - นั่นคือคำใดที่คุณจะเปลี่ยนเพื่อรักษาวิญญาณของคำตอบ แต่เอาความไม่เข้าใจที่คุณเข้าใจออกไป? คำตอบของฉันไม่ใช่การคาดเดาอย่างบ้าคลั่งจากมือใหม่ แต่ความรีบร้อน (ความบิดเบี้ยวความผิดพลาด) ในความคิดเห็นของคุณดูเหมือนจะถือว่าเป็น เป้าหมายคือเพื่อให้วิธีคิดเกี่ยวกับความแตกต่างกับจำนวนคำน้อยที่สุด
Ruben Bartelink

(BTW ฉันคุ้นเคยกับประเภทค่าประเภทอ้างอิงผ่านโดยการอ้างอิงผ่านโดยค่า COM และ C ++ คุณจะพบว่ามันมีประโยชน์ที่จะทำให้การอ้างอิงถึงแนวคิดเหล่านี้ในการชี้แจงของคุณ)
Ruben Bartelink

1
การอ้างอิงวัตถุถูกส่งผ่านตามค่า (ยกเว้นเมื่อใช้คำหลัก "ref" หรือ "out") คิดว่าวัตถุเป็นหมายเลขประจำตัว หากตัวแปรคลาสมี "Object # 1943" และส่งผ่านตัวแปรนั้นตามค่าของรูทีนรูทีนนั้นสามารถทำการเปลี่ยนแปลงกับ Object # 1943 ได้ แต่จะไม่สามารถทำให้ตัวแปรชี้ไปที่สิ่งอื่นใดนอกจาก "Object # 1943" หากตัวแปรถูกส่งผ่านโดยการอ้างอิงรูทีนอาจทำให้จุดตัวแปรนั้นค้างอยู่ "Object # 5441"
supercat

1
@supercat: ฉันชอบคำอธิบายของคุณอ้างอิง ref vs val (และ anaology ติดตามนี้) ฉันคิดว่าเคนนี่ไม่จำเป็นต้องอธิบายสิ่งนี้กับเขา (ค่อนข้าง) สับสนในขณะที่ความคิดเห็นของเขา ฉันหวังว่าเราทุกคนจะสามารถลบความคิดเห็น goddam เหล่านี้แม้ว่าพวกเขาเพียงแค่ทำให้ทุกคนสับสน สาเหตุที่แท้จริงของเรื่องไร้สาระทั้งหมดนี้ดูเหมือนว่าเคนนี่จะเข้าใจผิดคำตอบของฉันและยังไม่ได้ชี้ให้เห็นคำเดียวที่ควรจะเพิ่ม / ลบ / แทนที่ พวกเราสามคนไม่ได้เรียนรู้อะไรเลยจากการสนทนาที่เราไม่เคยรู้มาก่อนและคำตอบอื่น ๆ มีจำนวน upvotes ที่น่าหัวเราะ
Ruben Bartelink

18

ออก:

ใน C # วิธีสามารถส่งกลับค่าเดียวเท่านั้น หากคุณต้องการคืนค่ามากกว่าหนึ่งค่าคุณสามารถใช้คำหลักที่ไม่ใช้ โมดิฟายเออร์ส่งออกคืนเป็นค่าตอบแทนโดยอ้างอิง คำตอบที่ง่ายที่สุดคือใช้คำว่า "ออก" เพื่อรับค่าจากวิธีการ

  1. คุณไม่จำเป็นต้องเริ่มต้นค่าในฟังก์ชั่นการโทร
  2. คุณต้องกำหนดค่าในฟังก์ชั่นที่เรียกมิฉะนั้นคอมไพเลอร์จะรายงานข้อผิดพลาด

Ref:

ใน C # เมื่อคุณส่งค่าประเภทเช่น int, float, double ฯลฯ เป็นอาร์กิวเมนต์ไปยังพารามิเตอร์ method จะถูกส่งผ่านตามค่า ดังนั้นถ้าคุณปรับเปลี่ยนค่าพารามิเตอร์มันจะไม่ส่งผลกระทบต่อการโต้แย้งในการเรียกวิธีการ แต่ถ้าคุณทำเครื่องหมายพารามิเตอร์ด้วยคีย์เวิร์ด“ ref” มันจะสะท้อนให้เห็นในตัวแปรจริง

  1. คุณต้องเริ่มต้นตัวแปรก่อนที่จะเรียกใช้ฟังก์ชัน
  2. ไม่จำเป็นต้องกำหนดค่าใด ๆ ให้กับพารามิเตอร์การอ้างอิงในวิธีการ หากคุณไม่เปลี่ยนค่าจำเป็นต้องทำเครื่องหมายว่า "อ้างอิง" คืออะไร

"ใน C # เมธอดสามารถส่งคืนได้เพียงหนึ่งค่าเท่านั้นหากคุณต้องการคืนค่ามากกว่าหนึ่งค่าคุณสามารถใช้คีย์เวิร์ด out" นอกจากนี้เรายังสามารถใช้ "อ้างอิง" เพื่อคืนค่า ดังนั้นเราสามารถใช้ทั้ง ref และ out ถ้าเราต้องการคืนค่าหลายค่าจากวิธี?
เน็ด

1
ใน c # 7 คุณสามารถส่งคืนค่าได้หลายค่าด้วย ValueTuples
Iman Bahrampour

13

ตัวอย่างสุนัข, แมว วิธีที่สองที่มีการอ้างอิงเปลี่ยนวัตถุที่อ้างอิงโดยผู้โทร ดังนั้น "Cat" !!!

    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

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

ตัวอย่าง:

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

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


5
ใช้ได้เฉพาะเมื่อไม่มีการสร้างและส่งคืนวัตถุใหม่ เมื่อมีการสร้างวัตถุใหม่การอ้างอิงไปยังวัตถุเก่าจะหายไป
etsuba

8
นี่เป็นสิ่งที่ผิด - ลองทำสิ่งต่อไปนี้: เพิ่มsomeObject = nullเพื่อBarสิ้นสุดการดำเนินการ รหัสของคุณจะทำงานได้ดีเนื่องจากBarการอ้างอิงถึงอินสแตนซ์นั้นเป็นโมฆะเท่านั้น ตอนนี้เปลี่ยนBarเป็นBar(ref MyClass someObject)และดำเนินการอีกครั้ง - คุณจะได้รับNullReferenceExceptionเนื่องจากFooการอ้างอิงถึงอินสแตนซ์นั้นเป็นโมฆะเช่นกัน
Keith

8

refและoutทำงานในทำนองเดียวกันยกเว้นความแตกต่างดังต่อไปนี้

  • refตัวแปรจะต้องเริ่มต้นก่อนการใช้งาน outสามารถใช้ตัวแปรโดยไม่ได้รับมอบหมาย
  • outพารามิเตอร์จะต้องถือว่าเป็นค่าที่ไม่ได้กำหนดโดยฟังก์ชั่นที่ใช้มัน ดังนั้นเราสามารถใช้outพารามิเตอร์เริ่มต้นในรหัสโทร แต่ค่าจะหายไปเมื่อฟังก์ชั่นการดำเนินการ

8

สำหรับผู้ที่เรียนรู้จากตัวอย่าง (เหมือนผม) นี่คือสิ่งที่แอนโธนี Kolesov ไม่ว่าจะเป็น

ฉันได้สร้างตัวอย่างการอ้างอิงออกและอื่น ๆ เพื่ออธิบายประเด็น ฉันไม่ได้ครอบคลุมแนวปฏิบัติที่ดีที่สุดเป็นเพียงตัวอย่างเพื่อทำความเข้าใจความแตกต่าง

https://gist.github.com/2upmedia/6d98a57b68d849ee7091


6

"เบเกอร์"

นั่นเป็นเพราะคนแรกเปลี่ยนการอ้างอิงสตริงของคุณให้ชี้ไปที่ "คนทำขนมปัง" การเปลี่ยนการอ้างอิงเป็นไปได้เพราะคุณผ่านการอ้างอิงคำหลัก (=> การอ้างอิงถึงการอ้างอิงถึงสตริง) สายที่สองได้รับสำเนาของการอ้างอิงไปยังสตริง

สตริงดูพิเศษบางอย่างในตอนแรก แต่สตริงเป็นเพียงคลาสอ้างอิงและถ้าคุณกำหนด

string s = "Able";

ดังนั้น s คือการอ้างอิงถึงคลาสสตริงที่มีข้อความ "สามารถ"! การกำหนดให้กับตัวแปรเดียวกันอีกครั้งผ่านทาง

s = "Baker";

ไม่เปลี่ยนสตริงเดิม แต่เพียงสร้างอินสแตนซ์ใหม่และปล่อยให้ชี้ไปที่อินสแตนซ์นั้น!

คุณสามารถลองด้วยตัวอย่างของรหัสต่อไปนี้:

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

คุณคาดหวังอะไร? สิ่งที่คุณจะได้รับคือ "สามารถ" ได้เพราะคุณเพียงแค่ตั้งค่าการอ้างอิงใน s เป็นอินสแตนซ์อื่นในขณะที่ s2 ชี้ไปที่อินสแตนซ์ดั้งเดิม

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


1
เผง ดังนั้นจึงไม่เป็นความจริงเลยที่จะพูดว่า "เมื่อคุณผ่านประเภทอ้างอิง (คลาส) ไม่จำเป็นต้องอ้างอิงการใช้"
Paul Mitchell

ในทางทฤษฎีมันถูกต้องที่จะพูดอย่างนั้นเพราะเขาเขียนว่า "เพื่อให้สามารถแก้ไขได้" ซึ่งไม่สามารถทำได้บนสตริง แต่เนื่องจากวัตถุที่ไม่เปลี่ยนรูปแบบ "ref" และ "out" นั้นมีประโยชน์มากสำหรับประเภทการอ้างอิง! (.Net มีคลาสที่ไม่เปลี่ยนรูปจำนวนมาก!)
mmmmmmmm

ใช่คุณพูดถูก. ฉันไม่คิดว่าวัตถุที่ไม่เปลี่ยนรูปเหมือนสตริงเพราะวัตถุส่วนใหญ่ไม่แน่นอน
Albic

1
นี่คือคำตอบที่ทำให้งงที่เห็นใน LQP เพื่อให้แน่ใจ ไม่มีสิ่งใดผิดปกติยกเว้นว่าดูเหมือนเป็นการตอบสนองต่อความคิดเห็นที่ยาวและทั่วถึง (เนื่องจากคำถามเดิมกล่าวถึง Able and Baker ในการแก้ไขใด ๆ ) ราวกับว่านี่เป็นฟอรัม ฉันเดาว่ายังไม่ได้แยกออกจริง ๆ เมื่อย้อนกลับไป
นาธาน Tuggy

6

refหมายความว่าค่าในพารามิเตอร์ ref ถูกตั้งค่าไว้แล้ววิธีสามารถอ่านและแก้ไขได้ การใช้คีย์เวิร์ด ref เหมือนกับการบอกว่าผู้เรียกมีหน้าที่รับผิดชอบในการเริ่มต้นค่าของพารามิเตอร์


ออกบอกคอมไพเลอร์ว่าการเริ่มต้นของวัตถุเป็นความรับผิดชอบของฟังก์ชั่นฟังก์ชั่นจะต้องกำหนดให้กับพารามิเตอร์ออก ไม่อนุญาตให้ปล่อยทิ้งไว้โดยไม่ได้กำหนด


5

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

ตัวอย่างต่อไปนี้แสดงให้เห็นถึงสิ่งนี้:

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: พารามิเตอร์อ้างอิงเป็นการอ้างอิงไปยังตำแหน่งหน่วยความจำของตัวแปร เมื่อคุณส่งพารามิเตอร์ตามการอ้างอิงซึ่งแตกต่างจากพารามิเตอร์ค่าตำแหน่งหน่วยเก็บข้อมูลใหม่จะไม่ถูกสร้างขึ้นสำหรับพารามิเตอร์เหล่านี้ พารามิเตอร์อ้างอิงแสดงตำแหน่งหน่วยความจำเดียวกันกับพารามิเตอร์จริงที่ให้มากับวิธีการ

ใน C # คุณประกาศพารามิเตอร์อ้างอิงโดยใช้คำหลักอ้างอิง ตัวอย่างต่อไปนี้แสดงให้เห็นถึงสิ่งนี้:

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

การอ้างอิงและการทำงานเช่นเดียวกับการส่งต่อโดยการอ้างอิงและการส่งผ่านโดยตัวชี้เช่นเดียวกับใน C ++

สำหรับการอ้างอิงข้อโต้แย้งจะต้องประกาศและเริ่มต้น

สำหรับออกอาร์กิวเมนต์ต้องประกาศ แต่อาจหรือไม่อาจเริ่มต้น

        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
out double Half_nbrคุณสามารถประกาศตัวแปรแบบอินไลน์:
Sebastian Hofmann

4

เวลาการเขียน:

(1) เราสร้างวิธีการโทร Main()

(2) มันจะสร้างรายการวัตถุ (ซึ่งเป็นชนิดที่อ้างอิงวัตถุ) myListและเก็บไว้ในตัวแปร

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

ระหว่างรันไทม์:

(3) รันไทม์จัดสรรหน่วยความจำบนสแต็กที่ # 00 กว้างพอที่จะเก็บที่อยู่ (# 00 = myListเนื่องจากชื่อตัวแปรเป็นเพียงชื่อแทนจริง ๆ สำหรับตำแหน่งหน่วยความจำ)

(4) รันไทม์สร้างรายการวัตถุบนกองที่ตำแหน่งหน่วยความจำ #FF (ที่อยู่เหล่านี้ทั้งหมดเป็นตัวอย่างเพื่อประโยชน์)

(5) รันไทม์จะเก็บที่อยู่เริ่มต้น #FF ของวัตถุที่ # 00 (หรือในคำเก็บการอ้างอิงของวัตถุรายการในตัวชี้myList)

กลับไปที่เวลาการเขียน:

(6) จากนั้นเราจะส่งรายการวัตถุเป็นอาร์กิวเมนต์myParamListไปยังวิธีการที่เรียกmodifyMyListและกำหนดวัตถุรายการใหม่ให้กับมัน

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

List<int> newList = ModifyMyList(myList)

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

ระหว่างรันไทม์:

(7) รันไทม์เริ่มรูทีนการเรียกสำหรับเมธอดที่เรียกใช้และเป็นส่วนหนึ่งของมันตรวจสอบชนิดของพารามิเตอร์

(8) ตามหาชนิดการอ้างอิงนั้นจะจัดสรรหน่วยความจำในกองที่ # 04 สำหรับ aliasing myParamListตัวแปรพารามิเตอร์

(9) จากนั้นจะเก็บค่า #FF ไว้ในนั้นเช่นกัน

(10) รันไทม์สร้างรายการวัตถุบนกองที่ตำแหน่งหน่วยความจำ # 004 และแทนที่ #FF ใน # 04 ด้วยค่านี้ (หรือยกเลิกการลงรายการวัตถุต้นฉบับดั้งเดิมและชี้ไปที่วัตถุรายการใหม่ในวิธีนี้)

ที่อยู่ใน # 00 จะไม่เปลี่ยนแปลงและเก็บการอ้างอิงถึง #FF (หรือmyListตัวชี้ดั้งเดิมไม่ถูกรบกวน)


เตะคำหลักที่เป็นคำสั่งคอมไพเลอร์ที่จะข้ามรุ่นรหัสรันไทม์สำหรับ (8) และ (9) ซึ่งหมายความว่าจะไม่มีการจัดสรรกองสำหรับพารามิเตอร์วิธี มันจะใช้ตัวชี้ # 00 ดั้งเดิมเพื่อทำงานกับวัตถุที่ #FF หากตัวชี้ดั้งเดิมไม่เริ่มต้นรันไทม์จะหยุดบ่นไม่สามารถดำเนินการต่อเนื่องจากตัวแปรไม่ได้เริ่มต้น

ออกคำหลักที่เป็นคำสั่งคอมไพเลอร์ที่สวยมากเป็นเช่นเดียวกับโทษที่มีการปรับเปลี่ยนเล็กน้อยที่ (9) และ (10) คอมไพเลอร์คาดว่าอาร์กิวเมนต์จะไม่กำหนดค่าเริ่มต้นและจะดำเนินการต่อด้วย (8), (4) และ (5) เพื่อสร้างวัตถุบนฮีปและเก็บที่อยู่เริ่มต้นในตัวแปรอาร์กิวเมนต์ ไม่มีข้อผิดพลาดจะถูกโยนทิ้งและการอ้างอิงใด ๆ ที่เก็บไว้ก่อนหน้านี้จะหายไป


3

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

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

    • วิธีการโทรจะเห็นการเปลี่ยนแปลงเช่นsomeClass.Message = "Hello World"ว่าคุณจะใช้ref, outหรือไม่มีอะไร
    • การเขียนsomeClass = new MyClass()ข้างในนั้นจะmyFunction(someClass)สลับวัตถุที่มองเห็นโดยsomeClassอยู่ในขอบเขตของmyFunctionวิธีการเท่านั้น วิธีการโทรยังคงทราบเกี่ยวกับMyClassอินสแตนซ์ดั้งเดิมที่สร้างและส่งผ่านไปยังวิธีการของคุณ
  • คุณต้องการ refหรือoutถ้าคุณวางแผนที่จะแลกเปลี่ยนsomeClassออกสำหรับวัตถุใหม่ทั้งหมดและต้องการวิธีการโทรเพื่อดูการเปลี่ยนแปลงของคุณ

    • การเขียนsomeClass = new MyClass()ภายในmyFunction(out someClass)เปลี่ยนวัตถุที่มองเห็นได้ด้วยวิธีการที่เรียกว่าmyFunction

มีโปรแกรมเมอร์คนอื่นอยู่

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

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

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

โดยวิธีการใน C # 7.2 มีการinแก้ไขด้วย

และนั่นจะเป็นการป้องกันไม่ให้วิธีการเปลี่ยนอินสแตนซ์ที่ส่งผ่านเป็นอินสแตนซ์อื่น ลองคิดดูว่าการพูดคุยกับนักพัฒนาหลายล้านคนนั้น "ส่งการอ้างอิงตัวแปรดั้งเดิมของคุณมาให้ฉันและฉันสัญญาว่าจะไม่แลกเปลี่ยนข้อมูลที่สร้างขึ้นอย่างพิถีพิถันเพื่อสิ่งอื่น" inมีลักษณะเฉพาะบางอย่างและในบางกรณีเช่นที่การแปลงโดยนัยอาจจำเป็นต้องทำให้คุณสั้นเข้ากันได้กับin intคอมไพเลอร์จะทำให้ int ชั่วคราวขยายสั้นของคุณไปมันผ่านมันโดยการอ้างอิงและเสร็จสิ้น มันสามารถทำได้เพราะคุณได้ประกาศว่าคุณจะไม่ไปยุ่งกับมัน


Microsoft ทำสิ่งนี้ด้วย.TryParseวิธีการกับชนิดตัวเลข:

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

โดยตั้งค่าสถานะพารามิเตอร์outที่พวกเขากำลังแข็งขันประกาศที่นี่ "เราจะแน่นอนจะเปลี่ยนค่าที่สร้างขึ้นอย่างระมัดระวังของคุณ 98234957 ออกอย่างอื่น"

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

public void PoorlyNamedMethod(out SomeClass x)

คุณสามารถเห็นว่ามันเป็นoutและคุณสามารถรู้ได้ว่าถ้าคุณใช้เวลาเป็นชั่วโมง ๆ ในการสร้างตัวเลขที่สมบูรณ์แบบ:

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

นั่นเป็นการเสียเวลาเปล่าใช้เวลาทั้งหมดนี้เพื่อทำให้ชั้นเรียนนั้นสมบูรณ์แบบ แน่นอนว่ามันจะถูกโยนทิ้งไปและแทนที่ด้วย PoorlyNamedMethod


3

สำหรับผู้ที่ต้องการคำตอบที่กระชับ

ทั้งสองrefและคำหลักที่ใช้ในการส่งทีละoutreference


ตัวแปรของrefคำหลักจะต้องมีค่าหรือต้องอ้างถึงวัตถุหรือnull ก่อนที่จะผ่าน


แตกต่างจากrefตัวแปรของoutคำหลักจะต้องมีค่าหรือต้องอ้างถึงวัตถุหรือnull หลังจากผ่านไปเช่นเดียวกับไม่จำเป็นต้องมีค่าหรืออ้างถึงวัตถุก่อนที่จะผ่าน


2

เพื่อแสดงคำอธิบายที่ยอดเยี่ยมมากมายฉันได้พัฒนาแอพคอนโซลต่อไปนี้:

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: สำเนาของStringListชื่อLiStriจะถูกส่งผ่าน เมื่อเริ่มต้นวิธีการสำเนานี้อ้างอิงรายการดั้งเดิมและสามารถใช้แก้ไขรายการนี้ได้ ต่อมาLiStriอ้างอิงอีกList<string>วัตถุภายในวิธีการซึ่งไม่ได้ส่งผลกระทบต่อรายชื่อเดิม

  • HalloWelt: เป็นนามแฝงของเริ่มต้นได้แล้วLiStriRef วัตถุที่ListStringRefผ่านList<string>ถูกใช้เพื่อเริ่มต้นใหม่จึงrefมีความจำเป็น

  • CiaoMondo: LiStriOutเป็นนามแฝงของListStringOutและจะต้องเริ่มต้น

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


1

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

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

พารามิเตอร์ Ref ใช้สำหรับข้อมูลที่อาจแก้ไขได้พารามิเตอร์ out ใช้สำหรับข้อมูลที่เป็นเอาต์พุตเพิ่มเติมสำหรับฟังก์ชัน (เช่น int.TryParse) ที่ใช้ค่าส่งคืนสำหรับบางสิ่งอยู่แล้ว


1

ด้านล่างนี้ผมได้แสดงให้เห็นตัวอย่างการใช้ทั้งRefและออก ตอนนี้คุณทุกคนจะถูกล้างเกี่ยวกับการอ้างอิงและออก

ในตัวอย่างที่กล่าวถึงด้านล่างเมื่อฉันแสดงความคิดเห็น// myRefObj = myClass ใหม่ {ชื่อ = "อ้างอิงภายนอกเรียกว่า !!"}; สายจะได้รับข้อผิดพลาดว่า"การใช้ตัวแปรท้องถิ่นที่ไม่ได้กำหนด 'myRefObj"แต่ไม่มีข้อผิดพลาดดังกล่าวในการออก

ตำแหน่งที่จะใช้ Ref : เมื่อเรากำลังเรียกโพรซีเดอร์ที่มีพารามิเตอร์ in และพารามิเตอร์เดียวกันจะถูกใช้เพื่อเก็บเอาต์พุตของ proc นั้น

ตำแหน่งที่จะใช้ Out:เมื่อเราเรียกโพรซีเดอร์ที่ไม่มีพารามิเตอร์และพารามิเตอร์เดียวกันจะถูกใช้เพื่อส่งคืนค่าจาก proc นั้น ยังบันทึกผลลัพธ์

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

คุณสามารถตรวจสอบรหัสนี้มันจะอธิบายถึงความแตกต่างที่สมบูรณ์ของมันเมื่อคุณใช้ "ref" หมายความว่าคุณได้เริ่มต้น int / string นั้นแล้ว

แต่เมื่อคุณใช้ "out" มันจะทำงานในทั้งสองเงื่อนไขเมื่อคุณเริ่มต้น int / string หรือไม่ แต่คุณต้องเริ่มต้น int / string นั้นในฟังก์ชั่นนั้น


1

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

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

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     

Ref และ out ในวิธีการบรรทุกเกินพิกัด

ทั้ง ref และ out ไม่สามารถใช้ในการ overloading method พร้อมกันได้ อย่างไรก็ตามการอ้างอิงและออกจะได้รับการปฏิบัติแตกต่างกันในเวลาทำงาน แต่จะได้รับการปฏิบัติเหมือนกันในเวลารวบรวม (CLR ไม่ได้แยกความแตกต่างระหว่างทั้งสองในขณะที่มันสร้าง IL สำหรับการอ้างอิงและออก)


0

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

จากมุมมองของผู้โทร, C # จะในหลาย ๆ สถานการณ์สมมติว่าเมื่อเรียกเมธอดที่มีoutพารามิเตอร์จะทำให้ตัวแปรที่ส่งผ่านถูกเขียนโดยไม่ต้องอ่านก่อน สมมติฐานนี้อาจไม่ถูกต้องเมื่อเรียกวิธีการเขียนในภาษาอื่น ตัวอย่างเช่น:

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

หากmyDictionaryระบุการIDictionary<TKey,TValue>ใช้งานที่เขียนในภาษาอื่นที่ไม่ใช่ C # ถึงแม้ว่าMyStruct s = new MyStruct(myDictionary);ดูเหมือนว่าจะมีการมอบหมาย แต่ก็อาจทำให้sไม่ได้รับการแก้ไข

โปรดทราบว่าตัวสร้างที่เขียนใน VB.NET ซึ่งแตกต่างจากใน C # ไม่ได้ตั้งสมมติฐานว่าเมธอดที่เรียกว่าจะแก้ไขoutพารามิเตอร์ใด ๆหรือไม่และล้างฟิลด์ทั้งหมดโดยไม่มีเงื่อนไข พฤติกรรมแปลก ๆ ที่กล่าวถึงข้างต้นจะไม่เกิดขึ้นกับรหัสที่เขียนใน VB ทั้งหมดหรือทั้งหมดใน C # แต่สามารถเกิดขึ้นได้เมื่อโค้ดที่เขียนใน C # เรียกวิธีการเขียนใน VB.NET


0

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


-3

โปรดทราบว่าพารามิเตอร์อ้างอิงที่ส่งผ่านภายในฟังก์ชั่นนั้นทำงานได้โดยตรง

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

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

นี่จะเขียน Dog ไม่ใช่ Cat ดังนั้นคุณควรทำงานโดยตรงใน ObjectObject


6
ในขณะที่ทุกสิ่งที่นี่เป็นความจริง แต่ก็ไม่ได้อธิบายความแตกต่างระหว่างมูลค่าตามการอ้างอิงหรือออก อย่างน้อยครึ่งหนึ่งจะอธิบายความแตกต่างระหว่างการอ้างอิงและค่า / ประเภทที่ไม่เปลี่ยนรูป
Conrad Frix

หากคุณต้องการให้รหัสนั้นเขียน cat โปรดส่งผ่านวัตถุนั้นพร้อมกับคีย์ 'ref' เช่นนี้: public void Bar (อ้างอิง MyClass someObject บางส่วน), Bar (อ้างอิง myObject);
Daniel Botero Correa

-4

ฉันอาจไม่เก่งในเรื่องนี้ แต่แน่นอนว่าสตริง (แม้ว่าพวกเขาจะเป็นประเภทอ้างอิงทางเทคนิคและอาศัยอยู่บนฮีป) จะถูกส่งผ่านตามค่าไม่ใช่การอ้างอิง?

        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"!!!!

นี่เป็นสาเหตุที่คุณต้องอ้างอิงถ้าคุณต้องการให้การเปลี่ยนแปลงมีอยู่นอกขอบเขตของฟังก์ชันทำให้คุณไม่ได้ผ่านการอ้างอิงเป็นอย่างอื่น

เท่าที่ฉันรู้ว่าคุณต้องการอ้างอิงสำหรับ structs / value types และ string เท่านั้นเนื่องจาก string เป็นประเภทอ้างอิงที่อ้างว่าเป็น แต่ไม่ใช่ประเภทค่า

ฉันอาจจะผิดอย่างสมบูรณ์ที่นี่แม้ว่าฉันใหม่


5
ยินดีต้อนรับสู่ Stack Overflow, Edwin สตริงถูกส่งผ่านโดยการอ้างอิงเช่นเดียวกับวัตถุอื่น ๆ เท่าที่ฉันรู้ คุณอาจสับสนเพราะสตริงเป็นวัตถุที่ไม่เปลี่ยนรูปดังนั้นจึงไม่ชัดเจนว่ามันถูกส่งผ่านโดยการอ้างอิง ลองนึกภาพว่าสตริงมีวิธีการที่เรียกCapitalize()ว่าจะเปลี่ยนเนื้อหาของสตริงเป็นตัวพิมพ์ใหญ่ หากคุณแทนที่บรรทัดa = "testing";ด้วยa.Capitalize();ผลลัพธ์ของคุณจะเป็น "HELLO" ไม่ใช่ "Hello" ข้อดีอย่างหนึ่งของประเภทที่ไม่เปลี่ยนรูปคือคุณสามารถส่งผ่านการอ้างอิงและไม่ต้องกังวลกับรหัสอื่นที่เปลี่ยนค่า
Don Kirkby

2
มีความหมายพื้นฐานสามประเภทที่ชนิดหนึ่งสามารถเปิดเผยได้: ความหมายอ้างอิงที่ไม่แน่นอน, ความหมายของค่าที่ไม่แน่นอนและความหมายที่ไม่เปลี่ยนรูป พิจารณาตัวแปร x และ y ของประเภท T ซึ่งมีเขตข้อมูลหรือคุณสมบัติ m และถือว่า x ถูกคัดลอกไปยัง y ถ้า T มีซีแมนทิกส์อ้างอิงการเปลี่ยนแปลง xm จะถูกสังเกตโดย ym ถ้า T มีซีแมนทิกส์ตามตัวอักษรเราสามารถเปลี่ยน xm ได้โดยไม่ส่งผลกระทบต่อ ym ถ้า T มีซีแมนทิกส์ที่ไม่เปลี่ยนรูป ซีแมนทิกส์ที่ไม่เปลี่ยนรูปสามารถจำลองได้โดยการอ้างอิงหรือวัตถุที่มีค่า สตริงเป็นวัตถุอ้างอิงที่ไม่เปลี่ยนรูป
supercat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.