ฉันกำลังสร้างฟังก์ชั่นที่ฉันต้องผ่านวัตถุเพื่อให้สามารถแก้ไขได้โดยฟังก์ชั่น อะไรคือความแตกต่างระหว่าง:
public void myFunction(ref MyClass someClass)
และ
public void myFunction(out MyClass someClass)
ฉันควรใช้แบบใดและเพราะเหตุใด
ฉันกำลังสร้างฟังก์ชั่นที่ฉันต้องผ่านวัตถุเพื่อให้สามารถแก้ไขได้โดยฟังก์ชั่น อะไรคือความแตกต่างระหว่าง:
public void myFunction(ref MyClass someClass)
และ
public void myFunction(out MyClass someClass)
ฉันควรใช้แบบใดและเพราะเหตุใด
คำตอบ:
ref
บอกคอมไพเลอร์ว่าวัตถุถูกเริ่มต้นก่อนที่จะเข้าฟังก์ชั่นในขณะที่out
บอกคอมไพเลอร์ว่าวัตถุจะเริ่มต้นได้ภายในฟังก์ชั่น
ดังนั้นในขณะที่ref
เป็นสองวิธีout
คือออกเท่านั้น
ref
วิธีการปรับปรุงที่:
out
วิธีการปรับปรุงที่:
out
สามารถอ่านได้ทั้งหมดในวิธีการก่อนที่จะถูกกำหนดโดยวิธีการนั้นถ้ามันได้รับการเริ่มต้นก่อนที่วิธีการที่ถูกเรียก? ฉันหมายถึงวิธีการที่เรียกสามารถอ่านสิ่งที่วิธีการโทรผ่านไปเป็นอาร์กิวเมนต์?
สมมติว่า Dom ปรากฏตัวที่ห้องเล็ก ๆ ของ Peter เกี่ยวกับบันทึกช่วยจำเกี่ยวกับรายงาน TPS
ถ้า Dom เป็นอาร์กิวเมนต์ ref เขาจะมีสำเนาบันทึกที่พิมพ์ออกมา
ถ้าดอมเป็นข้อโต้แย้งเขาจะทำให้ปีเตอร์พิมพ์สำเนาบันทึกใหม่เพื่อให้เขานำติดตัวไปด้วย
ฉันจะลองทำตามคำอธิบาย:
ฉันคิดว่าเราเข้าใจว่าประเภทค่าทำงานอย่างไร ประเภทค่าคือ (int, long, struct ฯลฯ ) เมื่อคุณส่งพวกเขาในการทำงานโดยไม่มีคำสั่งเตะมันสำเนาข้อมูล ทุกสิ่งที่คุณทำกับข้อมูลนั้นในฟังก์ชั่นจะมีผลเฉพาะกับการคัดลอกไม่ใช่ต้นฉบับ คำสั่ง ref ส่งข้อมูลจริงและการเปลี่ยนแปลงใด ๆ จะส่งผลต่อข้อมูลภายนอกฟังก์ชั่น
ตกลงไปยังส่วนที่สับสนประเภทการอ้างอิง:
ให้สร้างประเภทการอ้างอิง:
List<string> someobject = new List<string>()
เมื่อคุณใหม่บางอย่างโครงการสองส่วนจะถูกสร้างขึ้น:
ตอนนี้เมื่อคุณส่งบางอย่างลงในเมธอดโดยไม่มีการอ้างอิงให้คัดลอกตัวชี้อ้างอิงไม่ใช่ข้อมูล ดังนั้นตอนนี้คุณมีสิ่งนี้:
(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
ref
และout
พารามิเตอร์
out
คำหลักได้หรือไม่
คุณควรใช้out
ตามความต้องการของคุณ
ใน C # วิธีสามารถส่งกลับค่าเดียวเท่านั้น หากคุณต้องการคืนค่ามากกว่าหนึ่งค่าคุณสามารถใช้คำหลักที่ไม่ใช้ โมดิฟายเออร์ส่งออกคืนเป็นค่าตอบแทนโดยอ้างอิง คำตอบที่ง่ายที่สุดคือใช้คำว่า "ออก" เพื่อรับค่าจากวิธีการ
ใน C # เมื่อคุณส่งค่าประเภทเช่น int, float, double ฯลฯ เป็นอาร์กิวเมนต์ไปยังพารามิเตอร์ method จะถูกส่งผ่านตามค่า ดังนั้นถ้าคุณปรับเปลี่ยนค่าพารามิเตอร์มันจะไม่ส่งผลกระทบต่อการโต้แย้งในการเรียกวิธีการ แต่ถ้าคุณทำเครื่องหมายพารามิเตอร์ด้วยคีย์เวิร์ด“ ref” มันจะสะท้อนให้เห็นในตัวแปรจริง
ตัวอย่างสุนัข, แมว วิธีที่สองที่มีการอ้างอิงเปลี่ยนวัตถุที่อ้างอิงโดยผู้โทร ดังนั้น "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;
}
เนื่องจากคุณผ่านประเภทการอ้างอิง (คลาส) จึงไม่จำเป็นต้องใช้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
ถ้าคุณต้องการเปลี่ยนวัตถุภายในวิธีการของคุณ
someObject = null
เพื่อBar
สิ้นสุดการดำเนินการ รหัสของคุณจะทำงานได้ดีเนื่องจากBar
การอ้างอิงถึงอินสแตนซ์นั้นเป็นโมฆะเท่านั้น ตอนนี้เปลี่ยนBar
เป็นBar(ref MyClass someObject)
และดำเนินการอีกครั้ง - คุณจะได้รับNullReferenceException
เนื่องจากFoo
การอ้างอิงถึงอินสแตนซ์นั้นเป็นโมฆะเช่นกัน
ref
และout
ทำงานในทำนองเดียวกันยกเว้นความแตกต่างดังต่อไปนี้
ref
ตัวแปรจะต้องเริ่มต้นก่อนการใช้งาน out
สามารถใช้ตัวแปรโดยไม่ได้รับมอบหมายout
พารามิเตอร์จะต้องถือว่าเป็นค่าที่ไม่ได้กำหนดโดยฟังก์ชั่นที่ใช้มัน ดังนั้นเราสามารถใช้out
พารามิเตอร์เริ่มต้นในรหัสโทร แต่ค่าจะหายไปเมื่อฟังก์ชั่นการดำเนินการสำหรับผู้ที่เรียนรู้จากตัวอย่าง (เหมือนผม) นี่คือสิ่งที่แอนโธนี Kolesov ไม่ว่าจะเป็น
ฉันได้สร้างตัวอย่างการอ้างอิงออกและอื่น ๆ เพื่ออธิบายประเด็น ฉันไม่ได้ครอบคลุมแนวปฏิบัติที่ดีที่สุดเป็นเพียงตัวอย่างเพื่อทำความเข้าใจความแตกต่าง
"เบเกอร์"
นั่นเป็นเพราะคนแรกเปลี่ยนการอ้างอิงสตริงของคุณให้ชี้ไปที่ "คนทำขนมปัง" การเปลี่ยนการอ้างอิงเป็นไปได้เพราะคุณผ่านการอ้างอิงคำหลัก (=> การอ้างอิงถึงการอ้างอิงถึงสตริง) สายที่สองได้รับสำเนาของการอ้างอิงไปยังสตริง
สตริงดูพิเศษบางอย่างในตอนแรก แต่สตริงเป็นเพียงคลาสอ้างอิงและถ้าคุณกำหนด
string s = "Able";
ดังนั้น s คือการอ้างอิงถึงคลาสสตริงที่มีข้อความ "สามารถ"! การกำหนดให้กับตัวแปรเดียวกันอีกครั้งผ่านทาง
s = "Baker";
ไม่เปลี่ยนสตริงเดิม แต่เพียงสร้างอินสแตนซ์ใหม่และปล่อยให้ชี้ไปที่อินสแตนซ์นั้น!
คุณสามารถลองด้วยตัวอย่างของรหัสต่อไปนี้:
string s = "Able";
string s2 = s;
s = "Baker";
Console.WriteLine(s2);
คุณคาดหวังอะไร? สิ่งที่คุณจะได้รับคือ "สามารถ" ได้เพราะคุณเพียงแค่ตั้งค่าการอ้างอิงใน s เป็นอินสแตนซ์อื่นในขณะที่ s2 ชี้ไปที่อินสแตนซ์ดั้งเดิม
แก้ไข: สตริงยังไม่เปลี่ยนรูปซึ่งหมายความว่าไม่มีเพียงวิธีการหรือคุณสมบัติที่ปรับเปลี่ยนอินสแตนซ์สตริงที่มีอยู่ (คุณสามารถพยายามที่จะหาหนึ่งในเอกสาร แต่คุณจะไม่ครีบใด ๆ :-)) วิธีการจัดการสตริงทั้งหมดส่งคืนอินสแตนซ์สตริงใหม่! (นั่นเป็นเหตุผลที่คุณมักจะได้รับประสิทธิภาพที่ดีขึ้นเมื่อใช้คลาส StringBuilder)
refหมายความว่าค่าในพารามิเตอร์ ref ถูกตั้งค่าไว้แล้ววิธีสามารถอ่านและแก้ไขได้ การใช้คีย์เวิร์ด ref เหมือนกับการบอกว่าผู้เรียกมีหน้าที่รับผิดชอบในการเริ่มต้นค่าของพารามิเตอร์
ออกบอกคอมไพเลอร์ว่าการเริ่มต้นของวัตถุเป็นความรับผิดชอบของฟังก์ชั่นฟังก์ชั่นจะต้องกำหนดให้กับพารามิเตอร์ออก ไม่อนุญาตให้ปล่อยทิ้งไว้โดยไม่ได้กำหนด
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();
}
}
}
การอ้างอิงและการทำงานเช่นเดียวกับการส่งต่อโดยการอ้างอิงและการส่งผ่านโดยตัวชี้เช่นเดียวกับใน 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);
out double Half_nbr
คุณสามารถประกาศตัวแปรแบบอินไลน์:
เวลาการเขียน:
(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) เพื่อสร้างวัตถุบนฮีปและเก็บที่อยู่เริ่มต้นในตัวแปรอาร์กิวเมนต์ ไม่มีข้อผิดพลาดจะถูกโยนทิ้งและการอ้างอิงใด ๆ ที่เก็บไว้ก่อนหน้านี้จะหายไป
เช่นเดียวกับการอนุญาตให้คุณกำหนดตัวแปรของคนอื่นให้กับอินสแตนซ์อื่นของคลาสส่งคืนค่าหลายค่า ฯลฯใช้ref
หรือout
ให้คนอื่นรู้ว่าคุณต้องการอะไรจากพวกเขาและสิ่งที่คุณตั้งใจจะทำกับตัวแปรที่พวกเขามีให้
คุณไม่จำเป็นต้องมี ref
หรือout
ถ้าสิ่งที่คุณกำลังจะทำคือสิ่งที่ปรับเปลี่ยนภายในอินสแตนซ์ที่ถูกส่งผ่านในการโต้แย้งMyClass
someClass
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
ทำให้คำสั่งของ "ส่งตัวแปรตัวยึดตำแหน่งไปยังวิธีการของฉันมันไม่สำคัญว่ามันจะมีค่าหรือไม่คอมไพเลอร์จะบังคับให้ฉันกำหนดให้เป็นค่าใหม่ฉันรับประกันได้อย่างแน่นอนว่าวัตถุที่คุณชี้ไป ตัวแปรก่อนที่คุณจะเรียกวิธีการของฉันจะแตกต่างกันตามเวลาที่ฉันทำ
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
สำหรับผู้ที่ต้องการคำตอบที่กระชับ
ทั้งสอง
ref
และคำหลักที่ใช้ในการส่งทีละout
reference
ตัวแปรของ
ref
คำหลักจะต้องมีค่าหรือต้องอ้างถึงวัตถุหรือnull
ก่อนที่จะผ่าน
แตกต่างจาก
ref
ตัวแปรของout
คำหลักจะต้องมีค่าหรือต้องอ้างถึงวัตถุหรือnull
หลังจากผ่านไปเช่นเดียวกับไม่จำเป็นต้องมีค่าหรืออ้างถึงวัตถุก่อนที่จะผ่าน
เพื่อแสดงคำอธิบายที่ยอดเยี่ยมมากมายฉันได้พัฒนาแอพคอนโซลต่อไปนี้:
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
ประพฤติตนเหมือนเดิม
ความแตกต่างเพียงอย่างเดียวคือตัวแปรที่คุณส่งผ่านเป็นพารามิเตอร์ out ไม่จำเป็นต้องเริ่มต้นและวิธีการที่ใช้พารามิเตอร์ ref ต้องตั้งค่าเป็นบางสิ่ง
int x; Foo(out x); // OK
int y; Foo(ref y); // Error
พารามิเตอร์ Ref ใช้สำหรับข้อมูลที่อาจแก้ไขได้พารามิเตอร์ out ใช้สำหรับข้อมูลที่เป็นเอาต์พุตเพิ่มเติมสำหรับฟังก์ชัน (เช่น int.TryParse) ที่ใช้ค่าส่งคืนสำหรับบางสิ่งอยู่แล้ว
ด้านล่างนี้ผมได้แสดงให้เห็นตัวอย่างการใช้ทั้ง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; }
}
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 นั้นในฟังก์ชั่นนั้น
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 สำหรับการอ้างอิงและออก)
จากมุมมองของวิธีการที่ได้รับพารามิเตอร์ความแตกต่างระหว่าง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
หากคุณต้องการส่งผ่านพารามิเตอร์ของคุณเป็นอ้างอิงคุณควรเริ่มต้นมันก่อนที่จะส่งพารามิเตอร์ไปยังฟังก์ชั่นอื่น ๆ คอมไพเลอร์เองจะแสดงข้อผิดพลาด แต่ในกรณีที่พารามิเตอร์ออกคุณไม่จำเป็นต้องเริ่มต้นพารามิเตอร์วัตถุก่อนส่งผ่านไป วิธีการคุณสามารถเริ่มต้นวัตถุในวิธีการเรียกตัวเอง
โปรดทราบว่าพารามิเตอร์อ้างอิงที่ส่งผ่านภายในฟังก์ชั่นนั้นทำงานได้โดยตรง
ตัวอย่างเช่น,
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
ฉันอาจไม่เก่งในเรื่องนี้ แต่แน่นอนว่าสตริง (แม้ว่าพวกเขาจะเป็นประเภทอ้างอิงทางเทคนิคและอาศัยอยู่บนฮีป) จะถูกส่งผ่านตามค่าไม่ใช่การอ้างอิง?
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 เป็นประเภทอ้างอิงที่อ้างว่าเป็น แต่ไม่ใช่ประเภทค่า
ฉันอาจจะผิดอย่างสมบูรณ์ที่นี่แม้ว่าฉันใหม่
Capitalize()
ว่าจะเปลี่ยนเนื้อหาของสตริงเป็นตัวพิมพ์ใหญ่ หากคุณแทนที่บรรทัดa = "testing";
ด้วยa.Capitalize();
ผลลัพธ์ของคุณจะเป็น "HELLO" ไม่ใช่ "Hello" ข้อดีอย่างหนึ่งของประเภทที่ไม่เปลี่ยนรูปคือคุณสามารถส่งผ่านการอ้างอิงและไม่ต้องกังวลกับรหัสอื่นที่เปลี่ยนค่า
MyClass
จะเป็นclass
ประเภทเช่นประเภทอ้างอิง ในกรณีดังกล่าววัตถุที่คุณผ่านสามารถแก้ไขmyFunction
ได้ด้วยการไม่มีคำหลักref
/ จะได้รับการอ้างอิงใหม่ที่ชี้ไปที่วัตถุเดียวกันและสามารถแก้ไขวัตถุเดียวกันได้มากเท่าที่ต้องการ ความแตกต่างของคำหลักที่จะทำให้จะได้รับที่ได้รับเดียวกันอ้างอิงถึงวัตถุเดียวกัน นั่นจะมีความสำคัญต่อเมื่อต้องเปลี่ยนการอ้างอิงให้ชี้ไปที่วัตถุอื่นout
myFunction
ref
myFunction
myFunction