Value and Reference Types
ก่อนอื่นต้องเข้าใจก่อนว่าอ็อบเจ็คที่สร้างจาก structs จะเป็น value types(รวมถึง Primitive types เช่น int, float, bool and char ด้วย) NFWจะจองหน่วยความจำไว้แหล่งเดียว(stack) ส่วนอ็อบเจ็คที่สร้างจาก classes จะเป็น reference types NFWจะจองหน่วยความจำไว้ 2 แหล่ง คือ heap สำหรับเก็บ อ็อบเจ็ค และ stack สำหรับเก็บตัวอ้างอิง
ตัวอย่างต่อไปนี้จะคือการประกาศอ็อบเจ็คโดยสมมติว่า Point คือ struct และ Form คือ class
-
Point p1 = new Point(); // Point เป็น *struct*
-
Form f1 = new Form(); // Form เป็น *class*
โคดบรรทัดแรก ระบบจะทำการจองหน่วยความจำให้ p1 แหล่งเดียว แต่โคดบรรทัดที่สอง ระบบจองหน่วยความจำให้ f1 สองแหล่งด้วยกัน แหล่งที่หนึ่งสำหรับเก็บ Form อ็อบเจ็ค และ แหล่งที่สองสำหรับเก็บตัวอ้างอิง เพื่อให้เข้าใจได้ง่ายขึ้นเราจะเขียนโคดแบบยาวหน่อยดังนี้
-
Form f1; // จองหน่วยความจำให้ reference
-
f1 = new Form(); // จองหน่วยความจำให้ object
ถ้าเราคัดลอกอ็อบเจ็คไปเก็บที่ตัวแปรใหม่ดังนี้
-
Point p2 = p1;
-
Form f2 = f1;
ข้อมูลจาก p1 จะถูกคัดลอกมาใส่ไว้ใน p2 อย่างอิสระโดยแยก fields กันอย่างชัดเจน แต่สำหรับ f2 จะถูกคัดลอกมาเฉพาะตัวอ้างอิงเท่านั้น ดังนั้น ทั้ง f1 และ f2 จะชี้ไปที่อ็อบเจ็ค(ใน heap)ตัวเดียวกัน
ในการส่งค่าพารามิเตอร์ไปให้เมธอด ในภาษา c# พารามิเตอร์แบบ value-type จะส่งเป็น value ไปให้เมธอด หมายความว่า ข้อมูลที่ส่งไปจะเป็นการส่งข้อมูลจริง ๆ (p2 ถูกคัดลอก) ขณะที่พารามิเตอร์แบบ reference-types จะส่งตัวอ้างอิงไปให้เมธอด ดังตัวอย่าง
-
Point myPoint = new Point (0, 0); //ประกาศตัวแปร value-type
-
Form myForm = new Form(); //ประกาศตัวแปร reference-type
-
Test (myPoint, myForm); //Test is a เมธอดที่สร้างไว้ข้างล่างนี้
-
-
void Test (Point p, Form f)
-
-
{
-
p.X = 100; //จะไม่มีผลต่อ MyPoint since เพราะ p เป็นตัวที่คัดลอกมา
-
f.Text = "Hello, World!"; //คำสั่งนี้เป็นการเปลี่ยนcaptionของ myForm
-
//จะมีผลต่อ myForm ด้วยเพราะ ทั้ง f และ myForm
-
//ต่างก็ชี้ไปที่อ็อบเจ็คเดียวกัน
-
f = null; //ไม่มีผลต่อ myForm
-
-
}
การใส่ค่าให้ f เป็น null จะไม่มีผลกระทบต่อ myForm เพราะ f คัดลอกมาเฉพาะตัวอ้างอิง ไม่ได้คัดลอกอ็อบเจ็คมาเก็บไว้
การจัดสรรหน่วยความจำ
CLR (Common Language Runtime) จะจัดสรรหน่วยความจำให้อ็อบเจ็ค 2 แหล่งด้วยกัน คือ stack และ heap
stack มีโครงสร้างข้อมูลที่ใช้หลักการ เข้าก่อนออกหลัง เมื่อเมธอดถูกเรียก CLR จะทำการจองเนื้อที่ในหน่วยความจำใน stack บนสุด จากนั้นเมธอดก็ ผลัก(push)ข้อมูลเข้าไปใน stack เมื่อการใช้งานเมธอดเสร็จสมบูรณ์ CLR ก็จะลบหน่วยความจำที่จองออก(popping) ในทางตรงกันข้าม heap จะใช้โครงสร้างข้อมูลแบบ linklist ที่มีการจัดเก็บข้อมูลใน heap ที่ CLR ได้กันไว้จากหน่วยความจำ(แรม)ไว้ก่อนแล้ว ส่วนการจัดการกับหน่วยความจำที่ไม่มีความจำเป็นหรือไม่ได้ใช้แล้วจะมี garbage collector คอยจัดการเพื่อคืนหน่วยความจำให้ใน heap ให้ว่าง
เพื่อแสดงให้เห็นถึงการทำงานของ stack และ heap ให้พิจารณาตัวอย่างต่อไปนี้
-
void CreateNewTextBox()
-
{
-
TextBox myTextBox = new TextBox(); // TextBox คือ class
-
}
ในเมธอดนี้ เราสร้างตัวแปรท้องถิ่นที่เก็บตัวอ้างอิงเพื่ออ้างอิงไปยังอ็อบเจ็ค ตัวแปรท้องถิ่นถูกจัดเก็บใน stack ขณะที่ อ็อบเจ็คถูกจัดเก็บไว้ใน heap

stack มีไว้สำหรับเก็บ
- ตัวแปรท้องถิ่นและพารามิเตอร์ของ reference เช่น myTextBox reference
- ตัวแปรท้องถิ่นและพารามิเตอร์ของ Value-type (structs, integers, bools, chars, DateTimes)
heap มีไว้สำหรับเก็บ
- เนื้อหาของ reference (type objects)
- ข้อมูลทุกอย่างที่อยู่ใน reference-type อ็อบเจ็ค
ตัวอย่าง Windows Forms
ตัวอย่างนี้เป็นตัวอย่างการสร้างวินโดว์ฟอร์มในวินโดว์แอพพลิเคชั่น เราจะใช้ types ของ 2 ตัว
- Size คือ ใช้กำหนดค่าให้กำวินโดว์แบบ 2 มิติ (มี type เป็น struct)
- font คือ การกำหนดรูปแบบตัวอักษร (มี type เป็น class)
ทั้งสองจะอยู่ในเนมสเปส System.Drawing
-
Size s = new Size (100, 100); // struct = value type
-
Font f = new Font (“Arial”,10); // class = reference type

ต่อจากนั้นสร้างฟอร์มขึ้นมา Form จะอยู่ในเนมสเปส System.Windows.Forms
-
Form myForm = new Form();
ใส่ค่า ขนาด และ แบบอักษร ให้ฟอร์มด้วยการถ่ายค่าจากอ็อบเจ็ค s และ f ให้กับฟอร์ม properties
-
myForm.Size = s;
-
myForm.Font = f;
ในหน่วยความจำจะมีลักษณะดังรูป

จะเห็นได้ว่า s จะถูกคัดลอกเนื้อหามาไว้ใน Form อ็อบเจ็คเลย ส่วน f จะถูกคัดลองตัวอ้างอิงมา หมายความว่า ถ้า s ถูกเปลี่ยนค่าจะไม่มีผลกระทบต่อ form แต่ถ้า f ถูกเปลี่ยนค่า(ที่ตัวมันอ้างถึง) form ก็จะต้องเปลี่ยนไปด้วย

คอมเม้นท์ซะหน่อย