ใช้เมื่อใดใน vs ref vs out


383

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

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


18
outไม่สามารถอ่านตัวแปรที่ส่งผ่านการใช้ก่อนที่จะถูกกำหนดให้ refไม่มีข้อ จำกัด นี้ ดังนั้นมีที่
Corey Ogburn

17
กล่าวโดยย่อrefคือสำหรับการเข้า / ออกในขณะที่outเป็นพารามิเตอร์ที่ออกเท่านั้น
ทิมเอส.

3
คุณไม่ได้อะไรอย่างแน่นอน
tnw

4
นอกจากนี้outตัวแปรที่ต้องกำหนดให้ในฟังก์ชัน
Corey Ogburn

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

คำตอบ:


399

คุณควรใช้จนกว่าคุณจะต้องoutref

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

ยิ่งไปกว่านั้นมันยังแสดงให้ผู้อ่านของการประกาศหรือการโทรว่าค่าเริ่มต้นมีความเกี่ยวข้อง (และอาจเก็บรักษาไว้) หรือโยนออกไป

เนื่องจากความแตกต่างเล็กน้อยพารามิเตอร์ out ไม่จำเป็นต้องเริ่มต้น

ตัวอย่างสำหรับout:

string a, b;
person.GetBothNames(out a, out b);

โดยที่ GetBothNames เป็นวิธีการดึงค่าสองค่าแบบ atom วิธีจะไม่เปลี่ยนพฤติกรรมไม่ว่าจะเป็น a และ b หากการโทรไปยังเซิร์ฟเวอร์ในฮาวายการคัดลอกค่าเริ่มต้นจากที่นี่ไปยังฮาวายนั้นเป็นการสิ้นเปลืองแบนด์วิดท์ ตัวอย่างที่คล้ายกันโดยใช้การอ้างอิง:

string a = String.Empty, b = String.Empty;
person.GetBothNames(ref a, ref b);

อาจทำให้ผู้อ่านสับสนเนื่องจากดูเหมือนว่าค่าเริ่มต้นของ a และ b นั้นมีความเกี่ยวข้อง (แม้ว่าชื่อเมธอดจะระบุว่าพวกเขาไม่ใช่)

ตัวอย่างสำหรับref:

string name = textbox.Text;
bool didModify = validator.SuggestValidName(ref name);

นี่คือค่าเริ่มต้นที่เกี่ยวข้องกับวิธีการ


5
"นั่นไม่ใช่กรณีจริง ๆ " - คุณช่วยอธิบายได้ดีขึ้นว่าคุณหมายถึงอะไร
peterchen

3
คุณไม่ต้องการใช้refสำหรับค่าเริ่มต้น
C.Evenhuis

155
สำหรับลูกหลาน: ความแตกต่างอีกอย่างหนึ่งที่ไม่มีใครพูดถึงดังที่กล่าวไว้ที่นี่ ; สำหรับoutพารามิเตอร์ต้องใช้วิธีการเรียกเพื่อกำหนดค่าก่อนที่วิธีจะส่งคืน - คุณไม่ต้องทำอะไรกับพารามิเตอร์การอ้างอิง
brichins

3
@brichins โปรดดูที่ส่วน 'ความคิดเห็น (การเพิ่มชุมชน) ในลิงก์ที่คุณกล่าวถึง มันเป็นข้อผิดพลาดที่แก้ไขในเอกสารประกอบ VS 2008
Bharat Ram V

13
@brichins วิธีการที่เรียกว่าจะต้องมีการกำหนดค่าไม่ใช่วิธีการโทร zverev.eugene นี่คือสิ่งที่ถูกแก้ไขในเอกสาร VS 2008
Segfault

72

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

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


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

@brichins: ไม่สามารถทำได้ outพารามิเตอร์จะถือว่าเป็นไม่ได้กำหนดเมื่อเข้าสู่ฟังก์ชั่น คุณจะไม่สามารถตรวจสอบค่าของพวกเขาจนกว่าคุณจะกำหนดค่าบางอย่างเป็นครั้งแรก - ไม่มีทางที่จะใช้ค่าที่พารามิเตอร์มีเมื่อเรียกใช้ฟังก์ชัน
Ben Voigt

จริงคุณไม่สามารถเข้าถึงค่าก่อนการมอบหมายภายใน ฉันหมายถึงความจริงที่ว่าพารามิเตอร์ตัวเองสามารถนำมาใช้ในภายหลังในวิธีการ - มันไม่ได้ล็อค ไม่ว่าสิ่งนี้ควรจะทำจริงหรือไม่เป็นการอภิปรายที่แตกต่าง (ในการออกแบบ); ฉันแค่อยากจะชี้ให้เห็นว่ามันเป็นไปได้ ขอขอบคุณสำหรับการชี้แจง.
brichins

2
@ ดาว: มันสามารถใช้กับประเภทการอ้างอิงได้เพราะเมื่อคุณผ่านพารามิเตอร์ประเภทการอ้างอิงสิ่งที่คุณกำลังผ่านคือค่าของการอ้างอิงไม่ใช่วัตถุเอง ดังนั้นมันจึงยังคงผ่านค่า
Tarik

38

คุณถูกต้องในที่นั้นหมายถึงrefให้ทั้ง "ใน" และ "ออก" ฟังก์ชั่นในขณะที่outมีเพียงฟังก์ชั่น "ออก" มีบางสิ่งที่ต้องพิจารณา:

  1. outต้องการให้วิธีการยอมรับพารามิเตอร์ต้องในบางจุดก่อนส่งคืนกำหนดค่าให้กับตัวแปร คุณพบว่ารูปแบบนี้ในบางส่วนของข้อมูลคีย์ / ค่าเรียนจัดเก็บข้อมูลชอบที่คุณมีฟังก์ชั่นเช่นDictionary<K,V> TryGetValueฟังก์ชั่นนี้ใช้outพารามิเตอร์ที่เก็บค่าที่จะเป็นถ้าดึง มันจะไม่มีเหตุผลที่ผู้เรียกจะส่งค่าไปยังฟังก์ชั่นนี้ดังนั้นจึงoutใช้เพื่อรับประกันว่าค่าบางอย่างจะอยู่ในตัวแปรหลังจากการโทรแม้ว่าจะไม่ใช่ข้อมูล "ของจริง" (ในกรณีTryGetValueที่ กุญแจไม่มีอยู่)
  2. outและrefพารามิเตอร์จะ marshaled แตกต่างกันเมื่อจัดการกับรหัส interop

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

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


1
โปรดทราบว่าข้อกำหนดที่เรียกว่าวิธีการกำหนดค่าให้กับพารามิเตอร์ออกมีการบังคับใช้โดยคอมไพเลอร์ c # และไม่ได้โดย IL พื้นฐาน ดังนั้นไลบรารีที่เขียนใน VB.NET อาจไม่สอดคล้องกับแบบแผนนั้น
jmoreno

เสียงเหมือนการอ้างอิงนั้นจริง ๆ แล้วเทียบเท่ากับสัญลักษณ์การเลิกประชุมใน C ++ (*) การอ้างอิง Passby ใน C # จะต้องเทียบเท่ากับสิ่งที่ C / C ++ หมายถึงเป็นพอยน์เตอร์คู่ (ตัวชี้ไปยังตัวชี้) ดังนั้น ref ต้องอ้างอิงตัวชี้ที่ 1 ทำให้การเรียกวิธีการเข้าถึงตำแหน่งหน่วยความจำของวัตถุจริงในบริบท
ComeIn

ที่จริงผมจะแนะนำให้ถูกต้องTryGetValueจะใช้refและไม่outชัดเจนในกรณีที่ไม่ได้หาคีย์
NetMage

27

ขึ้นอยู่กับบริบทของการคอมไพล์ (ดูตัวอย่างด้านล่าง)

outและrefทั้งสองแสดงถึงการผ่านตัวแปรโดยการอ้างอิง แต่refต้องการตัวแปรที่จะเริ่มต้นก่อนที่จะผ่านซึ่งอาจเป็นความแตกต่างที่สำคัญในบริบทของ Marshaling (Interop: UmanagedToManagedTransition หรือในทางกลับกัน)

เตือน MSDN :

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

จากเอกสาร MSDN อย่างเป็นทางการ:

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

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

เราสามารถตรวจสอบว่าการแจ้งออกและการอ้างอิงนั้นเหมือนกันเมื่อการโต้แย้งได้รับมอบหมาย:

ตัวอย่าง CIL :

ลองพิจารณาตัวอย่างต่อไปนี้

static class outRefTest{
    public static int myfunc(int x){x=0; return x; }
    public static void myfuncOut(out int x){x=0;}
    public static void myfuncRef(ref int x){x=0;}
    public static void myfuncRefEmpty(ref int x){}
    // Define other methods and classes here
}

ใน CIL คำแนะนำmyfuncOutและmyfuncRefเหมือนกันตามที่คาดไว้

outRefTest.myfunc:
IL_0000:  nop         
IL_0001:  ldc.i4.0    
IL_0002:  starg.s     00 
IL_0004:  ldarg.0     
IL_0005:  stloc.0     
IL_0006:  br.s        IL_0008
IL_0008:  ldloc.0     
IL_0009:  ret         

outRefTest.myfuncOut:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldc.i4.0    
IL_0003:  stind.i4    
IL_0004:  ret         

outRefTest.myfuncRef:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldc.i4.0    
IL_0003:  stind.i4    
IL_0004:  ret         

outRefTest.myfuncRefEmpty:
IL_0000:  nop         
IL_0001:  ret         

nop : ไม่มีการดำเนินการ, ldloc : load local, stloc : stack local, ldarg : load argument, bs.s : branch to target ....

(ดู: รายการคำสั่ง CIL )


23

ด้านล่างเป็นบันทึกย่อบางส่วนที่ฉันดึงมาจากบทความ codeproject นี้ในC # Out Vs Ref

  1. มันควรจะใช้เฉพาะเมื่อเราคาดหวังว่าหลายเอาต์พุตจากฟังก์ชั่นหรือวิธีการ ความคิดเกี่ยวกับโครงสร้างอาจเป็นตัวเลือกที่ดีสำหรับสิ่งเดียวกัน
  2. REF และ OUT เป็นคำหลักที่กำหนดวิธีการส่งผ่านข้อมูลจากผู้โทรไปยังผู้รับสายและในทางกลับกัน
  3. ในข้อมูล REF ผ่านสองทาง จากผู้โทรไปยัง callee และในทางกลับกัน
  4. ในข้อมูลออกผ่านเพียงวิธีเดียวจากผู้โทรไปยังผู้โทร ในกรณีนี้หากผู้โทรพยายามส่งข้อมูลไปยังผู้โทรมันจะถูกมองข้าม / ปฏิเสธ

หากคุณเป็นคนที่มองเห็นแล้วโปรดดูวิดีโอ youtube นี้ซึ่งแสดงให้เห็นถึงความแตกต่างในทางปฏิบัติhttps://www.youtube.com/watch?v=lYdcY5zulXA

ภาพด้านล่างแสดงความแตกต่างทางสายตามากขึ้น

C # Out Vs Ref


1
one-way, two-wayแง่อาจถูกนำไปใช้ที่นี่ อันที่จริงพวกเขาเป็นทั้งสองทาง แต่พฤติกรรมเชิงแนวคิดของพวกเขาแตกต่างกันไปในการอ้างอิงพารามิเตอร์และค่า
ibubi

17

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

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

int x;
Foo(ref x); // error: x is uninitialized

void Bar(out int x) {}  // error: x was not written to

ตัวอย่างเช่นint.TryParseส่งคืนboolและยอมรับout intพารามิเตอร์:

int value;
if (int.TryParse(numericString, out value))
{
    /* numericString was parsed into value, now do stuff */
}
else
{
    /* numericString couldn't be parsed */
}

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

สำหรับrefคุณสามารถดูInterlocked.Increment:

int x = 4;
Interlocked.Increment(ref x);

Interlocked.Incrementxอะตอมเพิ่มค่าของ เนื่องจากคุณจำเป็นต้องอ่านxเพื่อเพิ่มมันนี่เป็นสถานการณ์ที่refเหมาะสมกว่า คุณสนใจสิ่งที่xเกิดขึ้นก่อนที่มันจะถูกส่งผ่านไปให้Incrementทั้งหมด

ในเวอร์ชั่นถัดไปของ C # มันจะเป็นไปได้ที่จะประกาศตัวแปรในoutพารามิเตอร์ซึ่งจะเพิ่มความสำคัญกับลักษณะเฉพาะของเอาต์พุตเท่านั้น:

if (int.TryParse(numericString, out int value))
{
    // 'value' exists and was declared in the `if` statement
}
else
{
    // conversion didn't work, 'value' doesn't exist here
}

ขอบคุณ zneak สำหรับคำตอบของคุณ แต่คุณช่วยอธิบายฉันได้ไหมว่าทำไมฉันถึงใช้พารามิเตอร์อ่านและเขียนไม่ได้?
Rajbir Singh

@RajbirSingh เนื่องจากoutพารามิเตอร์ไม่จำเป็นต้องเริ่มต้นดังนั้นคอมไพเลอร์จะไม่ยอมให้คุณอ่านจากoutพารามิเตอร์จนกว่าคุณจะเขียนอะไรลงไป
zneak

ฉันเห็นด้วยกับคุณ แต่ในตัวอย่างด้านล่างพารามิเตอร์ out สามารถใช้เป็นการอ่านและเขียน: string name = "myName"; โมฆะส่วนตัว OutMethod (outOut สตริงออก) {ถ้า (nameOut == "myName") {nameOut = "Rajbir Singh ในวิธีการออก"; }}
Rajbir Singh

1
@RajbirSingh ตัวอย่างของคุณไม่ได้รวบรวม คุณไม่สามารถอ่านnameOutในifคำชี้แจงของคุณเพราะมันไม่ได้กำหนดอะไรมาก่อน
zneak

ขอบคุณ @zneak คุณพูดถูก มันไม่ได้รวบรวม ขอบคุณมากสำหรับความช่วยเหลือของฉันและตอนนี้มันทำให้รู้สึกถึงฉัน :)
rajbir ซิงห์

7

outเป็นรุ่นที่ จำกัด refมากขึ้นของ

ในเนื้อความเมธอดคุณต้องกำหนดให้กับoutพารามิเตอร์ทั้งหมดก่อนที่จะออกจากเมธอด นอกจากนี้ยังมีการoutละเว้นค่าที่กำหนดให้กับพารามิเตอร์ในขณะที่refกำหนดให้กำหนดค่าเหล่านั้น

ดังนั้นoutช่วยให้คุณทำ:

int a, b, c = foo(out a, out b);

ที่refจะต้องได้รับมอบหมายและ b


หากมีสิ่งใดoutเป็นรุ่นที่มีข้อ จำกัด น้อยกว่า refมี "Precondition: ตัวแปรถูกกำหนดแน่นอน Postcondition: ตัวแปรถูกกำหนดแน่นอน" ในขณะที่outมีเพียง `Postcondition: ตัวแปรถูกกำหนดแน่นอน" (และตามที่คาดไว้จำเป็นต้องมีการใช้ฟังก์ชั่นเพิ่มเติมโดยมีเงื่อนไขน้อยกว่า)
Ben Voigt

@BenVoigt: เดาว่าขึ้นอยู่กับทิศทางที่คุณมอง :) ฉันคิดว่าฉันหมายถึงข้อ จำกัด ในแง่ของความยืดหยุ่นในการเข้ารหัส (?)
leppie

7

ฟังแล้ว:

out = เพียงเริ่มต้น / เติมพารามิเตอร์ (พารามิเตอร์ต้องว่างเปล่า) ส่งคืนมันออกธรรมดา

อ้างอิง ref = อ้างอิงพารามิเตอร์มาตรฐาน (อาจมีค่า) แต่ฟังก์ชั่นสามารถปรับเปลี่ยนได้


ตัวแปรพารามิเตอร์ out สามารถรับค่าได้ก่อนที่คุณจะส่งผ่านไปยังเมธอด
Bence Végert

6

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

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

class OutExample
{
    static void Method(out int i)
    {
        i = 44;
    }
    static void Main()
    {
        int value;
        Method(out value);
        // value is now 44
    }
}

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

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

class CS0663_Example
{
    // Compiler error CS0663: "Cannot define overloaded 
    // methods that differ only on ref and out".
    public void SampleMethod(out int i) { }
    public void SampleMethod(ref int i) { }
}

การบรรทุกเกินพิกัดสามารถทำได้อย่างไรก็ตามหากวิธีการหนึ่งใช้อาร์กิวเมนต์refหรืออีกวิธีหนึ่งไม่ใช้วิธีoutนี้: C #

class OutOverloadExample
{
    public void SampleMethod(int i) { }
    public void SampleMethod(out int i) { i = 5; }
}

คุณสมบัติไม่ใช่ตัวแปรดังนั้นจึงไม่สามารถส่งผ่านเป็นoutพารามิเตอร์ได้

สำหรับข้อมูลเกี่ยวกับการผ่านอาร์เรย์ให้ดูที่การใช้อาร์เรย์การผ่านrefและout(คู่มือการเขียนโปรแกรม C #)

คุณไม่สามารถใช้refและoutคำหลักสำหรับวิธีต่อไปนี้:

Async methods, which you define by using the async modifier.

Iterator methods, which include a yield return or yield break statement.

ตัวอย่าง

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

class OutReturnExample
{
    static void Method(out int i, out string s1, out string s2)
    {
        i = 44;
        s1 = "I've been returned";
        s2 = null;
    }
    static void Main()
    {
        int value;
        string str1, str2;
        Method(out value, out str1, out str2);
        // value is now 44
        // str1 is now "I've been returned"
        // str2 is (still) null;
    }
}

6

วิธีการใช้งานinหรือoutหรือrefใน C #?

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

คุณไม่สามารถใช้in, refและoutคำหลักสำหรับประเภทต่อไปนี้วิธีการ:

  • เมธอด Asyncที่คุณกำหนดโดยใช้โมดิasyncฟายเออร์
  • วิธีการวนซ้ำซึ่งรวมถึงyield returnหรือyield breakคำสั่ง

5

เพียงชี้แจงให้ชัดเจนเกี่ยวกับความคิดเห็นของ OP ว่าการใช้ ref และ out เป็นการอ้างอิงถึงประเภทของมูลค่าหรือ struct ที่ประกาศอยู่นอกเมธอดซึ่งได้รับการยอมรับแล้วว่าไม่ถูกต้อง

พิจารณาการใช้การอ้างอิงกับ StringBuilder ซึ่งเป็นประเภทอ้างอิง:

private void Nullify(StringBuilder sb, string message)
{
    sb.Append(message);
    sb = null;
}

// -- snip --

StringBuilder sb = new StringBuilder();
string message = "Hi Guy";
Nullify(sb, message);
System.Console.WriteLine(sb.ToString());

// Output
// Hi Guy

ตามที่ระบุไว้ในที่นี้:

private void Nullify(ref StringBuilder sb, string message)
{
    sb.Append(message);
    sb = null;
}

// -- snip --

StringBuilder sb = new StringBuilder();
string message = "Hi Guy";
Nullify(ref sb, message);
System.Console.WriteLine(sb.ToString());

// Output
// NullReferenceException

4

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


4

ทำไมคุณต้องการที่จะใช้ออก?

เพื่อให้คนอื่นรู้ว่าตัวแปรนั้นจะเริ่มต้นเมื่อมันกลับมาจากวิธีการที่เรียกว่า!

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

ตัวอย่าง:

Car car;
SetUpCar(out car);
car.drive();  // You know car is initialized.

4

โดยทั่วไปทั้งrefและoutสำหรับการส่งผ่านวัตถุ / ค่าระหว่างวิธี

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

out : อาร์กิวเมนต์ไม่ได้เริ่มต้นและจะต้องเริ่มต้นในวิธีการ

ref : อาร์กิวเมนต์เริ่มต้นแล้วและสามารถอ่านและอัปเดตได้ในเมธอด

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

คุณสามารถเปลี่ยนการอ้างอิงที่ระบุเป็นอินสแตนซ์อื่น

เธอรู้รึเปล่า?

  1. แม้ว่าคีย์เวิร์ด ref และ out จะทำให้เกิดพฤติกรรมรันไทม์ที่แตกต่างกัน แต่ก็ไม่ถือว่าเป็นส่วนหนึ่งของลายเซ็นของเมธอดในเวลารวบรวม ดังนั้นวิธีการที่ไม่สามารถมากเกินไปถ้าความแตกต่างเพียงอย่างเดียวคือวิธีการหนึ่งที่ใช้อาร์กิวเมนต์การอ้างอิงและอื่น ๆ ที่จะใช้เวลาออกอาร์กิวเมนต์

  2. คุณไม่สามารถใช้คำหลักอ้างอิงและออกสำหรับวิธีต่อไปนี้:

    • เมธอด Async ซึ่งคุณกำหนดโดยใช้ตัวปรับเปลี่ยน async
    • วิธีการวนซ้ำซึ่งรวมถึงคำชี้แจงผลตอบแทนหรือผลตอบแทนการหยุดพัก
  3. คุณสมบัติไม่ใช่ตัวแปรดังนั้นจึงไม่สามารถส่งผ่านเป็นพารามิเตอร์ออกได้


4

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

public void PrintCoordinates(Point p)
{
  int x, y; // have to "predeclare"
  p.GetCoordinates(out x, out y);
  WriteLine($"({x}, {y})");
}

สามารถเขียนได้เช่นนี้

public void PrintCoordinates(Point p)
{
  p.GetCoordinates(out int x, out int y);
  WriteLine($"({x}, {y})");
}

ที่มา: มีอะไรใหม่ใน C # 7


4

ยังรู้สึกถึงความต้องการบทสรุปที่ดีนี่คือสิ่งที่ฉันคิดขึ้นมา

สรุป,

เมื่อเราอยู่ในฟังก์ชั่นนี่คือวิธีที่เราระบุการควบคุมการเข้าถึงข้อมูลตัวแปร

in = R

out = ต้อง W ก่อน R

ref = R + W


คำอธิบาย

in

ฟังก์ชั่นอาจอ่านได้เฉพาะตัวแปรนั้น

out

ตัวแปรจะต้องไม่ถูก initialised แรกเพราะ
ฟังก์ชั่นจะต้องเขียนไปก่อนที่จะอ่าน

ref

ฟังก์ชันอาจอ่าน / เขียนไปยังตัวแปรนั้นได้


ทำไมถึงตั้งชื่อเช่นนี้

มุ่งเน้นไปที่ข้อมูลที่ได้รับการแก้ไข

in

ต้องตั้งค่าข้อมูลก่อนเข้าฟังก์ชั่น (ใน)

out

ต้องตั้งค่าข้อมูลก่อนออกจากฟังก์ชั่น (ออก)

ref

ต้องตั้งค่าข้อมูลก่อนเข้าฟังก์ชั่น (ใน)
ข้อมูลอาจถูกตั้งค่าก่อนที่จะออกจากฟังก์ชั่น (ออก)


บางที (เข้า / ออก / อ้างอิง) ควรเปลี่ยนชื่อเป็น (r / wr / rw) หรืออาจจะไม่เข้า / ออกเป็นคำเปรียบเทียบที่ดีกว่า
คนจรจัด

0

ควรสังเกตว่าinเป็นคำหลักที่ถูกต้อง ณC # ver 7.2 :

ตัวดัดแปลงพารามิเตอร์มีให้ใน C # 7.2 และใหม่กว่า รุ่นก่อนหน้าสร้างข้อผิดพลาดของคอมไพเลอร์ CS8107 ("ฟีเจอร์ 'การอ้างอิงแบบอ่านอย่างเดียว' ไม่มีใน C # 7.0 โปรดใช้ภาษาเวอร์ชัน 7.2 หรือสูงกว่า") ในการกำหนดค่าเวอร์ชันภาษาคอมไพเลอร์ดูที่เลือกเวอร์ชันภาษา C #

...

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

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