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

  1. Point p1 = new  Point();         // Point เป็น *struct*
  2. Form f1 = new Form();           // Form เป็น  *class*

โคดบรรทัดแรก ระบบจะทำการจองหน่วยความจำให้ p1 แหล่งเดียว แต่โคดบรรทัดที่สอง ระบบจองหน่วยความจำให้ f1 สองแหล่งด้วยกัน แหล่งที่หนึ่งสำหรับเก็บ Form อ็อบเจ็ค และ แหล่งที่สองสำหรับเก็บตัวอ้างอิง เพื่อให้เข้าใจได้ง่ายขึ้นเราจะเขียนโคดแบบยาวหน่อยดังนี้

  1. Form f1;                    // จองหน่วยความจำให้ reference
  2. f1 = new Form();            // จองหน่วยความจำให้ object

ถ้าเราคัดลอกอ็อบเจ็คไปเก็บที่ตัวแปรใหม่ดังนี้

  1. Point p2 = p1;
  2. Form f2 = f1;

ข้อมูลจาก p1 จะถูกคัดลอกมาใส่ไว้ใน p2 อย่างอิสระโดยแยก fields กันอย่างชัดเจน แต่สำหรับ f2 จะถูกคัดลอกมาเฉพาะตัวอ้างอิงเท่านั้น ดังนั้น ทั้ง f1 และ f2 จะชี้ไปที่อ็อบเจ็ค(ใน heap)ตัวเดียวกัน

ในการส่งค่าพารามิเตอร์ไปให้เมธอด ในภาษา c# พารามิเตอร์แบบ value-type จะส่งเป็น value ไปให้เมธอด หมายความว่า ข้อมูลที่ส่งไปจะเป็นการส่งข้อมูลจริง ๆ (p2 ถูกคัดลอก) ขณะที่พารามิเตอร์แบบ reference-types จะส่งตัวอ้างอิงไปให้เมธอด ดังตัวอย่าง

  1. Point myPoint = new Point (0, 0);     //ประกาศตัวแปร value-type
  2. Form myForm = new Form();             //ประกาศตัวแปร reference-type
  3. Test (myPoint, myForm);               //Test is a เมธอดที่สร้างไว้ข้างล่างนี้
  4.  
  5. void Test (Point p, Form f)
  6.  
  7. {
  8. p.X = 100;                 //จะไม่มีผลต่อ MyPoint since เพราะ p เป็นตัวที่คัดลอกมา
  9. f.Text = "Hello, World!";  //คำสั่งนี้เป็นการเปลี่ยนcaptionของ myForm
  10.                            //จะมีผลต่อ myForm ด้วยเพราะ ทั้ง f และ myForm
  11.                            //ต่างก็ชี้ไปที่อ็อบเจ็คเดียวกัน
  12. f = null;                  //ไม่มีผลต่อ myForm
  13.  
  14. }

การใส่ค่าให้ 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 ให้พิจารณาตัวอย่างต่อไปนี้

  1. void CreateNewTextBox()
  2. {
  3. TextBox myTextBox = new TextBox();             // TextBox คือ class
  4. }

ในเมธอดนี้ เราสร้างตัวแปรท้องถิ่นที่เก็บตัวอ้างอิงเพื่ออ้างอิงไปยังอ็อบเจ็ค ตัวแปรท้องถิ่นถูกจัดเก็บใน 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

  1. Size s = new  Size (100, 100);       // struct = value type
  2. Font f = new Font (“Arial”,10);      // class = reference type

ต่อจากนั้นสร้างฟอร์มขึ้นมา Form จะอยู่ในเนมสเปส System.Windows.Forms

  1. Form myForm = new  Form();

ใส่ค่า ขนาด และ แบบอักษร ให้ฟอร์มด้วยการถ่ายค่าจากอ็อบเจ็ค s และ f ให้กับฟอร์ม properties

  1. myForm.Size = s;
  2. myForm.Font = f;

ในหน่วยความจำจะมีลักษณะดังรูป


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

http://www.albahari.com/value%20vs%20reference%20types.html