เมื่อฉันส่งผ่านstringไปยังฟังก์ชันตัวชี้ไปยังเนื้อหาของสตริงจะถูกส่งผ่านหรือไม่หรือสตริงทั้งหมดถูกส่งผ่านไปยังฟังก์ชันบนสแต็กอย่างที่structจะเป็น?
เมื่อฉันส่งผ่านstringไปยังฟังก์ชันตัวชี้ไปยังเนื้อหาของสตริงจะถูกส่งผ่านหรือไม่หรือสตริงทั้งหมดถูกส่งผ่านไปยังฟังก์ชันบนสแต็กอย่างที่structจะเป็น?
คำตอบ:
ผ่านการอ้างอิง อย่างไรก็ตามไม่ผ่านการอ้างอิงในทางเทคนิค นี่เป็นความแตกต่างที่ลึกซึ้ง แต่สำคัญมาก พิจารณารหัสต่อไปนี้:
void DoSomething(string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomething(strMain);
Console.WriteLine(strMain); // What gets printed?
}
มีสามสิ่งที่คุณต้องรู้เพื่อทำความเข้าใจว่าเกิดอะไรขึ้นที่นี่:
strMainก็ไม่ได้ถูกส่งผ่านโดยการอ้างอิง มันเป็นประเภทอ้างอิงแต่การอ้างอิงตัวเองจะผ่านค่า ทุกครั้งที่คุณส่งพารามิเตอร์โดยไม่มีrefคีย์เวิร์ด (ไม่นับoutพารามิเตอร์) คุณจะส่งผ่านค่าบางอย่างไปนั่นต้องหมายความว่าคุณ ... กำลังส่งข้อมูลอ้างอิงตามค่า เนื่องจากเป็นประเภทอ้างอิงจึงคัดลอกเฉพาะข้อมูลอ้างอิงลงในสแต็ก แต่นั่นหมายความว่าอย่างไร?
C # ตัวแปรมีทั้งประเภทอ้างอิงหรือประเภทค่า C # พารามิเตอร์มีทั้งผ่านอ้างอิงหรือผ่านค่า คำศัพท์เป็นปัญหาที่นี่ สิ่งเหล่านี้ฟังดูเหมือนกัน แต่ไม่ใช่
หากคุณส่งพารามิเตอร์ประเภทใดก็ได้และคุณไม่ได้ใช้refคำหลักนั้นคุณจะส่งผ่านค่านั้นไป หากคุณผ่านมันไปอย่างคุ้มค่าสิ่งที่คุณส่งผ่านมาคือสำเนา แต่ถ้าพารามิเตอร์เป็นประเภทการอ้างอิงสิ่งที่คุณคัดลอกมาคือข้อมูลอ้างอิงไม่ใช่สิ่งที่ชี้ไป
นี่คือบรรทัดแรกของMainวิธีการ:
string strMain = "main";
เราได้สร้างสองสิ่งในบรรทัดนี้: สตริงที่มีค่าที่mainเก็บไว้ในหน่วยความจำที่ใดที่หนึ่งและตัวแปรอ้างอิงที่เรียกว่าstrMainชี้ไปที่มัน
DoSomething(strMain);
DoSomethingตอนนี้เราผ่านการอ้างอิงถึงที่ เราผ่านมันไปอย่างคุ้มค่านั่นหมายความว่าเราทำสำเนา มันเป็นประเภทการอ้างอิงดังนั้นหมายความว่าเราคัดลอกข้อมูลอ้างอิงไม่ใช่สตริงเอง ตอนนี้เรามีการอ้างอิงสองรายการที่แต่ละจุดมีค่าเดียวกันในหน่วยความจำ
นี่คือวิธีการด้านบนDoSomething:
void DoSomething(string strLocal)
ไม่มีrefคำหลักดังนั้นstrLocalและstrMainเป็นการอ้างอิงที่แตกต่างกันสองรายการที่ชี้ไปที่ค่าเดียวกัน ถ้าเรากำหนดใหม่strLocal...
strLocal = "local";
... เราไม่ได้เปลี่ยนค่าที่เก็บไว้ เราใช้การอ้างอิงที่เรียกว่าstrLocalและมุ่งเป้าไปที่สตริงใหม่ล่าสุด จะเกิดอะไรขึ้นstrMainเมื่อเราทำเช่นนั้น? ไม่มีอะไร มันยังคงชี้ไปที่สายเก่า
string strMain = "main"; // Store a string, create a reference to it
DoSomething(strMain); // Reference gets copied, copy gets re-pointed
Console.WriteLine(strMain); // The original string is still "main"
มาเปลี่ยนสถานการณ์กันสักวินาที ลองนึกภาพว่าเราไม่ได้ทำงานกับสตริง แต่มีการอ้างอิงบางประเภทที่ไม่แน่นอนเช่นคลาสที่คุณสร้างขึ้น
class MutableThing
{
public int ChangeMe { get; set; }
}
หากคุณทำตามการอ้างอิงobjLocalถึงวัตถุที่ชี้ไปคุณสามารถเปลี่ยนคุณสมบัติได้:
void DoSomething(MutableThing objLocal)
{
objLocal.ChangeMe = 0;
}
ยังคงมีเพียงหนึ่งเดียวMutableThingในหน่วยความจำและทั้งการอ้างอิงที่คัดลอกและการอ้างอิงดั้งเดิมยังคงชี้ไปที่มัน คุณสมบัติของMutableThingตัวมันเองเปลี่ยนไป :
void Main()
{
var objMain = new MutableThing();
objMain.ChangeMe = 5;
Console.WriteLine(objMain.ChangeMe); // it's 5 on objMain
DoSomething(objMain); // now it's 0 on objLocal
Console.WriteLine(objMain.ChangeMe); // it's also 0 on objMain
}
อา แต่สตริงไม่เปลี่ยนรูป! ไม่มีChangeMeคุณสมบัติให้ตั้งค่า คุณไม่สามารถทำได้strLocal[3] = 'H'ใน C # เหมือนที่คุณทำได้กับcharอาร์เรย์สไตล์ C คุณต้องสร้างสตริงใหม่ทั้งหมดแทน วิธีเดียวที่จะเปลี่ยนแปลงได้strLocalคือชี้การอ้างอิงไปที่สตริงอื่นและนั่นหมายความว่าคุณไม่strLocalสามารถทำอะไรstrMainได้ ค่าไม่เปลี่ยนรูปและการอ้างอิงเป็นสำเนา
เพื่อพิสูจน์ว่ามีความแตกต่างนี่คือสิ่งที่จะเกิดขึ้นเมื่อคุณส่งผ่านข้อมูลอ้างอิงโดยอ้างอิง:
void DoSomethingByReference(ref string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomethingByReference(ref strMain);
Console.WriteLine(strMain); // Prints "local"
}
คราวนี้สตริงในMainมีการเปลี่ยนแปลงจริง ๆ เนื่องจากคุณส่งผ่านการอ้างอิงโดยไม่ได้คัดลอกลงในสแต็ก
ดังนั้นแม้ว่าสตริงจะเป็นประเภทการอ้างอิง แต่การส่งผ่านด้วยค่าหมายความว่าสิ่งที่เกิดขึ้นในการเรียกจะไม่ส่งผลกระทบต่อสตริงในตัวเรียก แต่เนื่องจากเป็นประเภทอ้างอิงคุณจึงไม่จำเป็นต้องคัดลอกสตริงทั้งหมดในหน่วยความจำเมื่อคุณต้องการส่งต่อ
refคำหลัก เพื่อพิสูจน์ว่าการส่งผ่านข้อมูลอ้างอิงสร้างความแตกต่างให้ดูการสาธิตนี้: rextester.com/WKBG5978
refคำหลักที่มียูทิลิตี้ผมก็แค่พยายามที่จะอธิบายว่าทำไมหนึ่งอาจคิดว่าการส่งผ่านชนิดการอ้างอิงโดยค่าใน C # ดูเหมือนว่า "ดั้งเดิม" (เช่น C) ความคิดของการส่งผ่านโดยการอ้างอิง (และผ่านชนิดการอ้างอิง โดยการอ้างอิงใน C # ดูเหมือนว่าจะส่งต่อการอ้างอิงไปยังการอ้างอิงตามค่า)
Foo(string bar)สามารถคิดได้อย่างไรในFoo(char* bar)ขณะที่Foo(ref string bar)จะเป็นFoo(char** bar)( Foo(char*& bar)หรือFoo(string& bar)ใน C ++) แน่นอนว่าไม่ใช่วิธีที่คุณควรคิดทุกวัน แต่ในที่สุดมันก็ช่วยให้ฉันเข้าใจสิ่งที่เกิดขึ้นภายใต้ประทุน
สตริงใน C # เป็นวัตถุอ้างอิงที่ไม่เปลี่ยนรูป ซึ่งหมายความว่าการอ้างอิงถึงพวกเขาจะถูกส่งไปรอบ ๆ (ตามค่า) และเมื่อสร้างสตริงแล้วคุณจะไม่สามารถแก้ไขได้ วิธีการที่สร้างสตริงเวอร์ชันแก้ไข (สตริงย่อยเวอร์ชันที่ถูกตัดแต่ง ฯลฯ ) สร้างสำเนาที่แก้ไขของสตริงต้นฉบับ
สตริงเป็นกรณีพิเศษ แต่ละอินสแตนซ์ไม่เปลี่ยนรูป เมื่อคุณเปลี่ยนค่าของสตริงคุณกำลังจัดสรรสตริงใหม่ในหน่วยความจำ
ดังนั้นเฉพาะการอ้างอิงเท่านั้นที่จะถูกส่งไปยังฟังก์ชันของคุณ แต่เมื่อมีการแก้ไขสตริงจะกลายเป็นอินสแตนซ์ใหม่และไม่ได้แก้ไขอินสแตนซ์เก่า
Uri(คลาส) และGuid (struct) ก็เป็นกรณีพิเศษเช่นกัน ฉันไม่เห็นว่าการSystem.Stringทำหน้าที่เหมือน "ประเภทค่า" เป็นอย่างไรมากกว่าประเภทอื่น ๆ ที่ไม่เปลี่ยนรูป ... ของคลาสหรือโครงสร้างต้นกำเนิด
Uri&Guid - คุณสามารถกำหนดค่าสตริง -ให้กับตัวแปรสตริงได้ สตริงดูเหมือนจะไม่แน่นอนเหมือนintถูกกำหนดใหม่ แต่เป็นการสร้างอ็อบเจกต์โดยปริยาย - ไม่มีnewคีย์เวิร์ด