ตอน 20 Assembly

ไปหน้าแรก | สารบัญ | Laploy.comระเบียนบทความ | บทความจากลาภลอย

เว็บไซต์นี้เป็นตัวอย่างเนื้อหาบางตอนในหนังสือ "เรียนรู้ด้วยตนเอง OOP C# ASP.NET" ครอบคลุม บทที่ 1 ถึงบทที่ 6 (ในหนังสือมี 21 บท) เนื้อหาในBlog อาจอาจแตกต่างจากในหนังสือเพราะเป็นเนื้อหาที่ยังไม่ได้ตรวจแก้ขัดเกลา (edit)

กดที่นี่เพื่อดูรายละเอียดเนื้อหาในแต่ละบท

กดที่นี่เพื่อไปยังเว็บบอร์ด ถาม-ตอบ 

 

Assembly

ในวงการนักเขียนโค้ดคำว่า assembly หมายถึงภาษาๆ หนึ่ง ซึ่งสูงกว่าภาษา machine ระดับหนึ่ง ใช้กันจำเฉพาะเจาะจงสำหรับตัวประมวลผลแต่ละเบอร์ หรือแต่ละตระกูล แต่ในภาษา C# assembly หมายถึงไฟล์ทั้งหมดใน project (ทั้งโค้ดและข้อมูล) ถูกคอมไพล์อัดรวมอยู่เป็นไฟล์เพียงไฟล์เดียว

โปรแกรมในภาษา C# อาจประกอบด้วย source code หนึ่งไฟล์หรือหลายๆ ไฟล์ก็ได้ คลาสหนึ่งคลาสอาจถูกนิยามไว้ภายในไฟล์เดียว (ปรกติจะมีนามสกุล .cs) หรือหลายๆ ไฟล์ก็ได้ ชื่อของไฟล์ source code อาจจะเป็นชื่อเดียวกับชื่อคลาสหรือไม่ก็ได้ (นิยมตั้งชื่อตรงกัน แต่จะไม่ทำเช่นนั้นก็ไม่ผิด)

ในโปรแกรมหนึ่งๆ หากท่านเขียนนิยาม type ไว้ในไฟล์ๆ หนึ่ง แล้ว จะอ้างถึง type นั้นจากไฟล์ๆ อื่นก็ได้ และทำได้ทันทีโดยไม่ต้องคำนึงถึงลำดับก่อนหลังใดๆ

เมื่อท่านคอมไพล์โปรแกรมให้เป็นโปรแกรมประยุกต์ ท่านจะได้ไฟล์นามสกุล .exe หากท่านคอมไพล์โปรแกรมให้เป็นไลบรารี (คือโปรแกรมที่ไม่มี method main()) ท่านจะได้ไฟล์นามสกุล .dll แม้ไฟล์ทั้งสองแบบนี้ จะมีนามสกุลเหมือนโปรแกรมแบบ Win32 แต่โครงสร้างภายในกลับไม่เหมือนกันเลย โดยไฟล์ assembly จะบรรจุโค้ดที่คอมไพล์แล้ว (เป็นภาษา MSIL ที่อยู่ในสภาพไบนารี) และข้อมูลแบบ metadata

 

คำว่า metadata เป็นคำจาร์กอนอีกคำที่สำคัญ เพราะท่านจะพบบ่อยในการเรียนภาษา C# เป็นคำที่เกิดจากการสมาสคำภาษากรีกสองคำ คือคำว่า meta (แปลว่า เหนือ เกี่ยวกับ หรือ แต่ไม่) และ data (พหูพจน์ของคำว่า datum ข้อมูล) ในที่นี้คำว่า metadata หมายถึง “ข้อมูลที่เกี่ยวกับข้อมูล” หรือข้อมูลที่ใช้เพื่ออธิบายข้อมูลอื่นๆ

metadata คือข้อมูลเลขฐานสองในรูปแบบ Portable Executable (PE) ข้อมูลนี้พรรณนาถึง type ภายใน assembly และ type ภายนอกที่โค้ดภายใน assembly เรียกใช้ ยกตัวอย่างเช่น metadata ของคลาส จะประกอบด้วยการพรรณนาถึงคลาสโดยละเอียด รวมถึงสมาชิกต่างๆ ภายในคลาส เช่น method พารามิเตอร์ของแต่ละ method เป็นต้น

ข้อดีประการสำคัญของ .NET คือ assembly แบบ .dll สามารถใช้งานข้ามภาษาได้ ยกตัวอย่างเช่นหากเพื่อนของท่านสร้าง type ด้วยภาษา VB.NET แล้วส่งให้ท่านเป็นไฟล์ .dll ท่านสามารถนำ type ในไลบรารีนั้นมาใช้งานในภาษา C# ได้โดยตรงราวกับเป็น type ที่สร้างด้วย C# ทุกประการ

Assembly คือ Portable executable file (PE) มีสองแบบคื่อ Process assembly (EXE) และ Libraly assembly (DLL) แบบ EXE จะสามารถนำไปคอมไพล์ (อีกครั้งโดย CLR) เพื่อใช้งานเป็นซอฟท์แวร์ประยุกต์ ขณะที่ DLL ใช้ทำไลบรารี นามสกุลไฟล์ไม่ใช่ตัวกำหนดชนิดของ PE สิ่งที่กำหนดคือ flag ใน PE ที่ compileจะตั้งให้เป็น process หรือ library ตามแต่เราจะสั่งตอนคอมไพล์

 

สมัยก่อน .NET การเขียนโปรแกรมแบบ Win32 โปรแกรมเมอร์ต้องลำบากในการควานหาฟังก์ชันที่ต้องการจากไฟล์ .dll จำนวนมาก ซึ่งไฟล์ .dll ของโปรแกรมประยุกต์ใน Win32 ถ้าจะให้ใช้งานร่วมกันได้ ก็จำเป็นต้องถูกจดทะเบียนเก็บรวมไว้ที่เดียวกันหมด (ปรกติจะอยู่ภายในโฟลเดอร์ Windows/system32) เมื่อมีโปรแกรมหลายตัวถูกติดตั้ง หรือเมื่อโปรแกรมเดิมเปลี่ยนเวอร์ชัน ความยุ่งยากที่เรียกว่า DLL Hell อาจเกิดขึ้นได้เสมอ

แนวคิดเรื่อง assembly เกิดขึ้นมาเพื่อแก้ปัญหา DLL Hell โดยตรง เพราะ DLL (หรือ EXE) แบบ .NET Framework จะถูกเก็บไว้ภายในโฟลเดอร์ของมันเอง ไม่ต้องมีการจดทะเบียนอะไรทั้งนั้น ดั้งนั้นโปรแกรมประยุกต์ใช้งานที่สร้างจากภาษา .NET ทั้งหมดจึงไม่ต้องการขบวนการติดตั้ง (setup หรือ install) ที่ยุ่งยากซับซ้อนเหมือนสมัย Win32 อันที่จริงแล้วเมื่อท่านต้องการติดตั้งโปรแกรมที่ท่านสร้างท่านสามารถใช้คำสั่ง xcopy คัดลอกไฟล์และโฟลเดอร์ทั้งหลายไปยังฮาร์ดดิสก์ของเครื่องปลายทางได้โดยตรง เมื่อต้องการยกเลิกการติดตั้ง (uninstall) ท่านก็เพียงแค่ลบไฟล์และโฟลเดอร์ที่เก็บโปรแกรมทิ้ง ไม่ต้องดำเนินการใดๆ กับ registry ของ Windows (เหมือนการติดตั้งโปรแกรมประยุกต์ในระบบปฏิบัติการของ Apple)

 

Field

field หรือ data field คือตัวแปรที่คู่กันกับคลาส หรือ instance ของคลาส (ก็คือ object) ใดๆ field อาจเป็นสมาชิกของคลาส หรือเป็นสมาชิกของ struct ก็ได้ field ทำให้เราสามารถสร้าง object ได้ตามหลักการ encapsulation โดยทำหน้าที่เก็บข้อมูลที่ใช้ในการประมวลผล field อาจมีภาวะเป็น object (reference type) หรือเป็นตัวแปรธรรมดา (value type) ก็ได้ โปรดดูตัวอย่างโค้ดต่อไปนี้

    class FooBar
    {
        int p;
        private object o;
    } 

คลาส FooBar มีสมาชิกเพียงสองตัว คือ p และ o ทั้งคู่เป็น field โดย p เป็นตัวแปรแบบ value type เหมือนตัวแปรธรรมดาทั่วไป ส่วน o เป็นตัวแปรแบบ object หรือตัวแปรแบบ reference type ในตัวอย่างข้างบน field o ถูกกำหนด access modifier เป็นแบบ private ส่วน p ไม่ได้ถูกกำหนด access modifier จึงกลายเป็น private ไปโดยปริยาย

ปรกติเราจะกำหนดให้ field มี access modifier ภาวะเป็น private เพื่อมิให้ client class หรือ derived class สามารถเข้าถึง field ได้ เมื่อต้องการให้ client class เปลี่ยนแปลงค่าของ filed เราจะให้ทำทางอ้อมผ่าน property แทน

อาจมีบางกรณีที่เราปรารถนาให้ client class สามารถอ่านค่าจาก field ได้ แต่ไม่ต้องการให้แก้ไขค่าของ field ในกรณีนี้เราจะกำหนด access modifier ของ field เป็น public และกำหนด modifier เป็น readonly ดังตัวอย่างต่อไปนี้

public readonly int foo;

ตัวอย่างโค้ดที่เห็นข้างบนเราประกาศ field ชื่อ foo ซึ่ง client class สามารถอ่านข้อมูลได้ แต่ไม่สามารถเขียนข้อมูล (เปลี่ยนแปลงข้อมูลใน field) ได้ (โปรดสังเกตลำดับการนิยามจะเริ่มจากซ้ายสุดเป็น access modifier ถัดมาเป็น type modifier และสุดท้ายติดกับชื่อ field คือ type)

filed ก็เหมือนตัวแปรทั่วไปที่เราสามารถกำหนดให้มี type เป็นอะไรก็ได้ เช่น int, string, double, decimal และเราอาจนิยาม field ให้เป็น array กี่มิติก็ได้

หากประสงค์จะนำคลาสไปใช้เป็น base class และต้องการให้ผู้นำคลาสไป inherit สามารถเข้าถึง field ได้ จะต้องกำหนด access modifier ของ field ให้เป็น protected field ที่มี access modifier แบบ protected จะสามารถเข้าถึงได้จาก derived class เท่านั้น โปรดดูตัวอย่างต่อไปนี้

1    class FooBar
2    {
3       protected int p;
4       public FooBar()
5       {
6          p = 12;
7       }
8    }
9    class NewFooBar : FooBar
10   {
11       public NewFooBar()
12       {
13          p++;
14       }
15       public int showP()
16       {
17          return p;
18       }
19    }
20    class Program
21    {
22       static void Main(string[] args)
23       {
24          NewFooBar myNFB = new NewFooBar();
25          Console.WriteLine(myNFB.showP());
26       }
27    } 

ในโค้ดนี้ base class คือ FooBar (บรรทัดที่ 1-8) คลาสที่สืบสันดานคือคลาส NewFooBar (บรรทัดที่ 9-19) และโค้ดที่ทดสอบการทำงานของ NewFooBar คือ method Main ในคลาส Program (บรรทัดที่ 20-27)

ในคลาส FooBar มีนิยาม field ชื่อ p ที่ถูกกำหนด access modifier ให้เป็น protected (บรรทัดที่ 3) ดังนั้นเมื่อคลาส NewFooBar อ้างถึง field ชื่อเดียวกัน (บรรทัดที่ 13) จะถือว่าเป็น field ตัวเดียวกัน เมื่อเราสร้าง object จากคลาส NewFooBar (บรรทัดที่ 24) ทั้ง constructor ของคลาส FooBar และ constructor ของคลาส NewFooBar จะถูกเรียกให้ทำงาน ผลลัพธ์ที่ปรากฏบนหน้าจอจึงเป็น

13

โปรดสังเกตว่าเราสร้าง method เพื่อให้ client class สามารถอ่านค่าจาก field ได้ เพราะ client class ไม่สามารถเข้าถึง field ที่มี access modifier เป็น private หรือ protect ได้โดยตรง method เช่นนี้ทำหน้าที่เหมือน property (ดูเรื่อง property ในหัวข้อถัดไป)

ข้อควรสังเกตอีกอย่างคือ derived class สามารถมองเห็นและใช้งาน field แบบ protected ที่นิยามไว้ใน base class ได้ราวกับเป็น filed ที่นิยามไว้ในตัว derived class เอง แต่จะไม่เป็นเช่นนั้นกับ filed แบบ private

ตามปรกติเมื่อเราสร้าง instance ของคลาสเราจะได้ object ในแต่ละ object จะมี filed ที่นิยามไว้ในคลาส เรียกว่า instance field สมมุติว่าเรานิยามคลาสให้มี field ชื่อ foo หากเราสร้าง instance ของคลาสนี้สิบ instance เราจะได้ object สิบตัว ใน object แต่ละตัวจะมี field ชื่อ foo เป็นของตัวเอง จึงเท่ากับเราได้ filed foo สิบตัวด้วย

ในทางกลับกัน หากเรากำหนดให้ field มี access modifier เป็น static จะทำให้ filed มีภาวะเป็น static field ซึ่งจะมีที่เก็บข้อมูลในหน่วยความจำเพียงตัวเดียวเท่านั้น ไม่ว่าเราจะสร้าง instance ของคลาสสักกี่ object ก็ตาม static field จะคงมีตัวเดียวเสมอ

 

ฟังก์ชันในภาษา C#

คำว่าฟังค์ชันในภาษา C และ C++ จะหมายถึงโปรแกรมย่อยหนึ่งโปรแกรมซึ่งทำหน้าที่ใดหน้าที่หนึ่ง เช่นฟังก์ชัน printf ทำหน้าที่แสดงผล แต่ในภาษา C# คำว่าฟังก์ชันกินความหมายกว้างกว่านั้น เพราะภาษา C# มีสมาชิกที่มีภาวะเป็นฟังค์ชันหลายแบบดังนี้

  • Method: ฟังก์ชันซึ่งเป็นตัวทำงานหลักของคลาส
  • Constructor: ฟังก์ชันที่ทำงานเมื่อมีการสร้าง instance ของคลาส
  • Destructor: ฟังก์ชันที่ทำงานเมื่อ object ถูกทำลาย
  • Property: ฟังก์ชันซึ่งทำหน้าที่อ่านเขียนค่ากับ client class
  • Indexer: ฟังก์ชันที่ทำให้เราจัดการ object ได้ราวกับเป็น array
  • Event: คือกลไกที่ช่วยให้ object สามารถส่งสัญญาณได้
  • Operator: ตัวกระทำเช่นบวก ลบ คูณ หาร

ฟังก์ชันในความหมายของโปรแกรมย่อยอย่าง printf ในภาษา C ในภาษา C# จะเรียกฟังก์ชันลักษณะนี้ว่า “สมาชิกแบบ method” เพราะในภาษา C# จะไม่มีฟังก์ชันลอยๆ อีกต่อไป ฟังก์ชันทุกฟังก์ชันจะต้องเป็นสมาชิกของคลาสใดคลาสหนึ่ง หรือ struct ใด struct หนึ่งเสมอ

 

properties

properties (อ่านว่า พร็อพเพอร์ตี) คือสมาชิกของคลาสซึ่งช่วยให้เรา อ่าน เขียน หรือคำนวณ ค่าของ private field ได้ แม้เราจะใช้งาน properties เหมือนกับว่ามันเป็นสมาชิกแบบ public data แต่อันที่จริงแล้วมันคือ method ชนิดพิเศษที่เรียกว่า accessors

ตามหลักการ encapsulation และ information hiding ของ OOP เราจะไม่อนุญาตให้ client class เข้าถึงส่วนเก็บข้อมูลของ object (คือ data filed) ได้โดยตรง เมื่อเราต้องการอ่านหรือเขียนค่าของ data filed ใน object เราจะใช้ properties ทำหน้าที่เป็นตัวกลางในการเชื่อมต่อแทน โปรดพิจารณาโค้ดตัวอย่างต่อไปนี้

1     class FooBar
2     {
3         private int power = 12;
4
5         public int Power
6         {
7             get { return power; }
8             set { power = value; }
9         }
10    }
11    class Program
12    {
13       static void Main(string[] args)
14       {
15           FooBar myFooBar = new FooBar();
16           Console.WriteLine(myFooBar.Power);
17           myFooBar.Power = 5;
18           Console.WriteLine(myFooBar.Power);
19           Console.ReadLine();
20       }
21    } 

โค้ดข้างบนนี้สาทิตการนิยาม properties อย่างง่ายที่สุด ผู้เขียนสร้างนิยามคลาสชื่อ FooBar (บรรทัดที่ 1-10) โดยนิยามสมาชิกเพียงสองตัว ตัวแรกชื่อ power เป็น data field (บรรทัดที่ 3) และกำหนดค่าเริ่มต้นให้เป็น 12 แม้ว่าผู้เขียนจะต้องการให้ client class สามารถเปลี่ยนแปลงค่าของ power ได้ แต่ก็ไม่ต้องการให้เข้าถึง power ได้โดยตรง จึงกำหนด access modifier ของ power ให้เป็น private

จากนั้นผู้เขียนสร้างนิยาม properties ชื่อ Power ซึ่งจะทำหน้าที่ให้ client class สามารถ อ่าน และเปลี่ยนแปลงค่าของ power ได้

โปรดสังเกตว่า power ตัวที่เป็น data field เขียนด้วยตัวเล็กทั้งหมด ส่วน Power ตัวที่เป็น properties เขียนโดยให้ตัวอักษรนำเป็นตัวใหญ่และตัวตามเป็นตัวเล็ก เนื่องจากภาษา C# เป็นภาษา case sensitive (แยกแยะตัวอักษรใหญ่-เล็กว่าต่างกัน) จึงมองว่า power และ Power เป็นชื่อที่ไม่ซ้ำกัน

Class diagram (แผนภูมิแสดงคลาส) ของคลาส FooBar และคลาส Program ตามโค้ดตัวอย่างข้างบน Fields แสดงด้วยภาพสี่เหลี่ยมสีน้ำเงิน method เป็นสี่เหลี่ยมสีชมภู และ Property เป็นภาพมือจับเอกสาร

 

การตั้งชื่อ data field และ properties ที่คู่กันให้มีชื่อตรงกัน แล้วทำให้แตกต่างกันโดยใช้ตัวอักษรนำเป็นธรรมเนียมปฏิบัติ (best practice) ที่ใช้กันตามปรกติ เพราะช่วยป้องกันให้ไม่เกิดความสับสนในการเขียนโปรแกรม แต่เราสามารถชื่อ data field และ properties ที่คู่กันให้มีชื่อแตกต่างกันโดยสิ้นเชิงก็ได้

keyword get และ return ในบรรทัดที่ 7 จะใช้คู่กันเสมอเพื่อให้ client class สามารถอ่านค่าของ data field ได้ keyword set และ value ในบรรทัดที่ 8 จะใช้คู่กันเสมอเพื่อให้ client class สามารถเปลี่ยนแปลงค่าของ data field ได้

ในตัวอย่างนี้โค้ดที่สาทิตการใช้งานคลาส FooBar คือ method Main ซึ่งเป็น method สมาชิกของคลาส Program โดยสร้าง object ชื่อ myFooBar (บรรทัดที่ 15) แล้วอ่านค่า data field power ผ่าน properties Power โดยใช้ชื่อ object ตามด้วยจุด และชื่อของ properties (myFooBar.Power บรรทัดที่ 16) การทำเช่นนี้จะทำให้โค้ดในคลาส FooBar บรรทัดที่ 7 ทำงาน คำสั่ง get จะนำค่าของ power ส่งกลับมาด้วยคำสั่ง return ซึ่งต้องมี return type ตรงกันทั้ง data field และ properties (ในตัวอย่างนี้เป็น int)

บรรทัดที่ 17 สาทิตการเปลี่ยนแปลงค่าของ data field power ผ่านทาง properties Power โดยใช้คำสั่ง

myFooBar.Power = 5;

การทำเช่นนี้มีผลให้โค้ดในคลาส FooBar บรรทัดที่ 8 ทำงาน คำสั่ง set จะนำเลข 5 ไปเป็นค่าของ value ซึ่งถูกนำไปกำหนดเป็นค่าของ data field power อีกต่อหนึ่ง

เมื่อรันโปรแกรมนี้ ผลลัพธ์บนจอภาพจะเป็นดังนี้

12

5

โปรดสังเกตว่า access modifier ของ properties จะเป็น public เสมอ

properties ช่วยให้เราสามารถคำนวณ เปลี่ยนแปลง หรือควบคุมค่าที่ client class ส่งมาได้ง่าย ยกตัวอย่างเช่น หากเราต้องการควบคุมไม่ให้ค่าของ power สูงกว่า 100 เราสามารถกำหนดใน properties Power ได้ดังนี้

    class FooBar
    {
        private int power = 12;

        public int Power
        {
            get { return power; }
            set
            {
                if (value < 100)
                {
                    power = value;
                }
                else
                {
                    power = 100;
                }
            }
        }

    }
    class Program
    {
        static void Main(string[] args)
        {
            FooBar myFooBar = new FooBar();
            Console.WriteLine(myFooBar.Power);
            myFooBar.Power = 50;
            Console.WriteLine(myFooBar.Power);
            myFooBar.Power = 150;
            Console.WriteLine(myFooBar.Power);
            Console.ReadLine();
        }
    }

ภายใน properties ส่วน get-value ผู้เขียนเพิ่มโค้ดตรวจสอบว่าค่าที่ส่งมาเกิน 100 หรือไม่ หากเกินให้กำหนดค่า power ไว้ที่ 100 เท่านั้น เมื่อรันโปรแกรมนี้จะได้ผลลัพธ์บนจอภาพดังนี้

12

50

100

ตอนต่อไป: Method

Post a comment or leave a trackback: Trackback URL.

ความเห็น

  • Unknown  On มกราคม 10, 2007 at 12:32 am

    ขอบพระคุณมากครับที่ให้ความรู้แก่คนไทยด้วยกัน ผมจะติดตามต่อไปนะครับ และถ้าผมเก่งเมื่อไหร่ผมก็จะให้ความรู้แก่คนอื่นๆบ้างเหมือนกัน

  • jacky  On พฤษภาคม 10, 2007 at 4:51 pm

    ขอบคุณมากครับ เช่นกันเมื่อผมเข้าใจอย่างถูกต้องแล้วผมก็จะเดินตามทางท่านเช่นกัน

  • phasook  On มิถุนายน 11, 2007 at 4:56 pm

    ขอบคุณมากครับ อ่านรอบแรกนึกว่าเข้าใจแล้ว พอลองเอาไปใช้งานจริง งงอยู่พักหนึ่ง มาทวนใหม่อีกทีถึงเข้าใจ ขอบคุณสำหรับสิ่งดี ๆ

  • กีรติพงศ์  On มกราคม 24, 2008 at 7:58 am

    ขอบพระคุณมากครับ

ใส่ความเห็น

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / เปลี่ยนแปลง )

Twitter picture

You are commenting using your Twitter account. Log Out / เปลี่ยนแปลง )

Facebook photo

You are commenting using your Facebook account. Log Out / เปลี่ยนแปลง )

Google+ photo

You are commenting using your Google+ account. Log Out / เปลี่ยนแปลง )

Connecting to %s

%d bloggers like this: