เมื่อฉันส่งผ่าน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
คีย์เวิร์ด