Monthly Archives: มกราคม 2007

ตอน 28 IEnumerator, Indexer และ Iterator

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

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

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

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

 

IEnumerator

IEnumerator ช่วยให้เราเข้าถึงชุดข้อมูลด้วยการวนซ้ำได้ ปรกติหากเราต้องการทำงานวนซ้ำเพื่อดึงข้อมูลใน array เราสามารถใช้คำสั่ง Foreach ดังนี้

static void Main(string[] args)
{
string[] engClass = { "Kanjana", "Wattana", "Bunchai" };
foreach (string student in engClass)
{
Console.WriteLine(student);
}
}

ตัวอย่างโค้ดข้างบนแสดงการวนซ้ำเพื่อดึงข้อมูลทั้งหมดใน array ชื่อ engClass (เก็บรายชื่อนักเรียนในวิชาภาษาอังกฤษ) โดยการใช้คำสั่ง Foreach สาเหตุที่เราใช้ Foreach กับ array ได้เพราะคลาส array ได้จัดให้มีอินเตอร์เฟส IEnumerator ไว้ภายใน หากเราสร้างชุดข้อมูล (custom data collection) ของเราเองขึ้นและต้องการให้ทำงานกับ Foreach ได้ก็สามารถทำได้เช่นกัน โดยจัดให้มีอินเตอร์เฟส IEnumerator ไว้ภายในคลาสของเรา ยกตัวอย่างเช่น แทนที่จะใช้ array เหมือนในตัวอย่างบน เรากลับสร้างคลาสเพื่อทำหน้าที่เป็น object ข้อมูลนักเรียนแทน โดยขั้นแรกเราต้องสร้างคลาส Student ก่อนดังนี้

public class Student
{
private string firstname;
public string Firstname
{
get { return firstname; }
set { firstname = value; }
}
public Student(string s)
{
firstname = s;
}
}

ขั้นต่อไปสร้างคลาสที่จะทำหน้าที่เป็น collection ของคลาส Student ดังนี้

    public class ClassList : IEnumerable
{
private ArrayList students;
public ClassList()
{
students = new ArrayList();
students.Add(new Student("somsree"));
students.Add(new Student("Kanjana"));
students.Add(new Student("Bunchar"));
}
public IEnumerator GetEnumerator();
}
private class AllStudent :
IEnumerator
{
}

โปรดสังเกตว่าในคลาส ClassList นี้ inherit จาก interface ชื่อ IEnumerable ซึ่งเป็นคลาสที่นิยามไว้ใน .NET Framework System.Collections เนื่องจากคลาส IEnumerable นี้มีนิยาม properties ชื่อ GetEnumerator() ดังนั้นคลาส ClassList จึงจำเป็นต้องใส่นิยามและโค้ดทำงานดังนี้

public IEnumerator GetEnumerator()
{
return (IEnumerator)new AllStudent(this);
}

โปรดสังเกตว่าสิ่งที่เราส่งกลับไปเป็นค่าของ properties นี้คือค่าที่ได้จากคลาส AllStudent() ซึ่งเป็นคลาสย่อยภายใน (nested class) ที่เราเขียนขึ้นเพื่อจัดการกับการวนรอบใน collection คลาส AllStudent() มีโค้ดดังนี้

    private class AllStudent : IEnumerator
{
private ClassList classList;
private int studentIndex;

public AllStudent(ClassList c)
{
classList = c;
studentIndex = -1;
}
public void Reset()
{
studentIndex = -1;
}
public object Current
{
get { return classList.students[studentIndex]; }
}
public bool MoveNext()
{
studentIndex++;
if (studentIndex >= classList.students.Count)
return false;
else
return true;
}
}

คลาส Student คือคลาสที่เราจะนำไปสร้าง object ที่ต้องการทำให้ใช้กับ Foreach ได้เหมือนarray ส่วน ClassList คือคลาสซึ่งทำหน้าที่เป็น object collection และคลาส AllStudent ทำหน้าที่ให้เกิดการวนรอบได้ โดยเราทำเป็น nested class ที่ inherit จาก interface IEnumerable

 

โปรดสังเกตว่าคลาสนี้ inherit จากคลาส IEnumerator ซึ่งเป็น interface ที่มีข้อกำหนดให้ derived class ต้องนิยามและใส่ส่วนโค้ด method สาม method คือ Reset(), Current() และ MoveNext() สุดท้ายคือโค้ดตัวอย่างการเรียกใช้งาน

public class MyClass
{
public static void Main()
{
ClassList myClass = new ClassList();
foreach (Student student in myClass)
Console.WriteLine(student.Firstname);
Console.ReadLine();
}
}

ผลลัพธ์ที่ได้จากการรันโปรแกรมนี้คือ

somsree
Kanjana
Bunchar

ข้อสรุปเรื่อง IEnumerator
• ใช้เพื่อให้ทำ Foreach กับ object collection ได้
• Array และ Queue ใช้ IEnumerator
• ทำงานได้กับหลาย client พร้อมๆ กัน
• เรียกใช้งานได้ผ่าน Foreach
• เรียกใช้โดยตรง (โดยไม่ผ่าน Foreach) ก็ได้

 

Indexer

Indexer คล้าย Properties แตกต่างกันที่เราใช้ properties เพื่ออ่านหรือเขียน data field ของ object แต่เราใช้ indexer เพื่ออ่านหรือเขียนค่าของตัว object เอง โดยเป็น object ที่มีชุดของ object ย่อยอยู่ภายใน (object collection)

ยกตัวอย่างเช่นสินค้าหมวดหนึ่งๆ จะมีสินค้ามากกว่าหนึ่งอย่าง เมื่อเรานิยามคลาส Catalog ทำหน้าที่แสดงหมวดสินค้า และคลาส Product ทำหน้าที่แสดงตัวสินค้า เราต้องหาทางทำให้คลาส Catalog มี object ซึ่งทำหน้าที่แสดง Product หลายๆ instance อยู่ภายใน จากนั้นเราก็ให้ class client เข้าถึง object ซึ่งทำหน้าที่แสดงสินค้า (คือ product) ในลักษณะเดียวกันทั้งหมด ซึ่งทำได้โดยใช้ indexer

Indexer ช่วยให้เราจัดการกับ instance ของคลาสหรือ struc ได้แบบเดียวกับ array อีกจุดหนึ่งที่ indexer แตกต่างจาก properties คือตัวเรียกใช้ (accessors คือ set และ get) ของ indexer จะมีพารามิเตอร์ด้วย ขณะที่ properties ไม่มี

ต่อไปนี้เป็นตัวอย่างโปรแกรมสาทิตการสร้างและใช้งาน indexer โดยเราจะสร้างคลาสสองคลาส คลาสแรกชื่อ ProductCatalog ทำหน้าที่เก็บข้อมูลสินค้า และมี indexer อยู่ภายในเพื่อให้ class client สามารถเรียกข้อมูลสินค้าโดยอ้างถึง object ได้เหมือน array อีกคลาสคือ Program เป็นคลาสแสดงวิธีเรียกใช้คลาสที่มี indxer

class ProductCatalog
{
string[] Product = { "Food", "Cloth", "Car" };
private int GetProductNumber(string testName)
{
int i = 100;
foreach (string productName in Product)
{
if (productName == testName)
{
return i;
}
i++;
}
return -1;
}
public int this[string productName]
{
get
{
return (GetProductNumber(productName));
}
}
}

โค้ดที่เห็นข้างบนคือนิยามคลาส ProductCatalog ซึ่งมี array ชื่อ Product ทำหน้าที่เป็นตัวอย่าง object เก็บข้อมูลสินค้าภายในคลาสนี้ (ในการใช้งานจริงเราอาจสร้าง object จาก struct หรือ class) มี method ชื่อ GetProductNumber ทำหน้าที่แสดงหมายเลขของสินค้าแต่ละชิ้น และมี indexer ซึ่งจะเรียกใช้งาน method นี้

class Program
{
static void Main(string[] args)
{
ProductCatalog myPD = new ProductCatalog();
Console.WriteLine(myPD["Food"]);
Console.WriteLine(myPD["Cloth"]);
Console.WriteLine(myPD["Car"]);
Console.WriteLine(myPD["House"]);
Console.Read();
}
}

Indexer ช่วยให้เราเข้าถึงกลุ่ม object ที่อยู่ภายใน object collection โดยการอ้างดัชนีได้เหมือน array ภายในคลาสจะมี indexer ได้เพียงหนึ่งตัวซึ่งจะมีชื่อเดียวกับคลาส แต่เราอาจทำ overload ต่อ indexer ได้ ในภาพนี้ object คือสิ่งที่ถูกสร้างจากคลาส Product ส่วนคลาสที่นิยาม object collection คือ ProductCatalog

 

โค้ดที่เห็นข้างบนคือคลาส Program ทำหน้าที่แสดงตัวอย่างการใช้งาน indexer ที่อยู่ภายในคลาส ProductCatalog รายละเอียดการทำงานของโปรแกรมมีขั้นตอนดังนี้

  1. method Main สร้าง object จากคลาส ProductCatalog โดยใช้ชื่อตัวแปรอ้างอิงเป็น myPD นี่คือ object ที่เราต้องการใช้งานมันด้วยการไล่ไปตามดัชนีได้อย่าง array
  2. เมื่อสร้าง object แล้วเราสามารถใส่วงเล็บเหลี่ยมหลังชื่อตัวแปร myPD ได้เหมือน array ภายในวงเล็บเราใส่ชื่อสินค้าที่เราอยากรู้หมายเลขของมัน

  3. C# compiler จะรู้ว่าคำสั่ง myPD[“Food”] คือการเรียกใช้ indexer ที่อยู่ภายในคลาส ProductCatalog มันจึงไปทำงานที่คลาส ProductCatalog ใน indexer มีคำสั่ง get ซึ่งมีคำสั่ง return คืนค่าเป็น int (เพราะที่ส่วนประกาศของ indexer เรากำหนด type เป็น int ไว้) ภายในวงเล็บหลังคำสั่ง return เราเรียก method GetProductNumber โดยส่งค่า ProductName (ขณะนี้เป็น “Food”) ให้เป็นพารามิเตอร์
  4. จากนั้นโปรแกรมจะไปทำงานที่ method GetProductNumber ซึ่งมีคำสั่ง foreach ทำการวนรอบเพื่อสร้างหมายเลขสินค้า โดยเราต้องการเริ่มจากหมายเลข 100 ดังนั้น “Food” ซึ่งเป็นข้อมูลใน array element แรก จึงได้ผลลัพธ์เป็น 100

ผลลัพธ์ของการทำงานของโค้ดตัวอย่างคือ

100
101
102
-1

ข้อสรุปเกี่ยวกับ indexer

• indexer ช่วยให้เราเข้าถึงชุดของ object ภายใน collection ได้โดยใช้ดัชนี
• indexer จะมีชื่อเดียวกับคลาสที่มันเป็นสมาชิก
• ภายในหนึ่งคลาสมี indexer ได้เพียงตัวเดียว
• ทำ overload ต่อ indexer ได้
• indexer ใช้ this เพื่อนำเสนอ instance นั้นๆ ของ object
• indexer มี accessors (คือ set และ get) เช่นเดียวกับ properties
• accessors ของ indexer ต้องมีพารามิเตอร์
• indexer ใช้ index operator (คือวงเล็บเหลี่ยม) เช่นเดียวกับ array
• indexer เป็น static ไม่ได้
• indexer มีได้ภายในคลาส struct และ interface

 

Iterator, yield

เนื่องจากการใช้ IEnumerator ยุ่งยากเกินไป ภาษา C# 2.0 จึงจัดให้มี Iterator ขึ้นทดแทน Iterator คือ method ที่มี get/set เหมือ properties ช่วยให้เราใช้ foreach เพื่อเข้าถึงค่าในคลาสหรือ struct ได้โดยไม่ต้องใช้ IEnumerator

แทนที่จะใช้ IEnumerator เราก็เพียงสร้าง Iterator ซึ่งทำหน้าที่ท่องไปในโครงสร้างข้อมูลของคลาส เมื่อคอมไพเลอร์ตรวจพบ Iterator ในโปรแกรมของเรา มันจะสร้างเมธอด Current, MoveNext และ Dispose ของ IEnumerator ให้โดยอัตโนมัติ ต่อไปนี้เป็นตัวอย่างโค้ดสาทิตการสร้างและใช้งาน Iterator

public class EngClass : IEnumerable
{
string[] student = { "Somsak", "Boonme", "Vichai" };
public IEnumerator GetEnumerator()
{
for (int i = 0; i < student.Length; i++)
{
yield return student[i];
}
}
}

โค้ดข้างบนคือตัวอย่าง collection class ง่ายๆ ชื่อ EngClass ใช้เก็บชื่อนักเรียนในชั้นเรียนวิชาภาษาอังกฤษ โปรดสังเกตว่าคลาส EngClass inherit จาก interface ชื่อ IEnumerable ซึ่งเป็น interface ที่ถูกนิยามไว้ใน System.Collections

 

เราควรใช้ Iterator และ yield แทนการใช้ IEnumerator แบบเดิมที่ยุ่งยากกว่า เพราะการใช้งาน IEnumerator แบบเดิมเราต้องสร้างคลาสจัดการกับการวนรอบเอง ซึ่งเป็นคลาสที่ inherit จาก interface IEnumerator และมีนิยาม method Reset(), Current() และ MoveNext()

ส่วนการใช้งาน Iterator เราไม่ต้องสร้างคลาสจัดการ เราเพียงใช้คำสั่ง yield ร่วมกับ type IEnumerator compiler จะสร้างคลาสจัดการกับการวนรอบให้เรา ซึ่งเป็นคลาสที่ inherit จาก interface IEnumerator และมีนิยาม method Reset(), Current() และ MoveNext() โดยอัตโนมัติ (โค้ดที่สร้างให้จะปรากฏใน MSIL)

 

ในคลาส EngClass มี method ชื่อ GetEnumerator() ทำหน้าที่เป็น Iterator โดย method นี้มี return type เป็น IEnumerator และการส่งค่ากลับจะใส่ keyword yield ไว้หน้า keyword return ซึ่ง keyword yield ทำหน้าที่จัดให้มีค่าของ object ที่ถูกวนอ่านค่า (ในที่นี้คือ array student[]) ต่อไปเป็น โค้ดทดสอบการใช้งานตัว Iterator

class test
{
static void Main()
{
EngClass studentName = new EngClass();
foreach (string name in studentName)
{
Console.Write(name + " ");
}
}
}

โค้ดข้างบนสร้าง object collection ชื่อ studentName จาก Iterator EngClass จากนั้นเราจึงสามารถใช้คำสั่ง foreach เพื่อวนอ่านค่าทั้งหมดใน collection ได้

ผลลัพธ์จากการทำงานของโปรแกรมนี้คือ

Somsak Boonme Vichai

ข้อสรุปเรื่อง Iterator
• ใช้งานได้สะดวกกว่าการทำ IEnumerator แบบเดิม
• Iterator ต้องใช้ร่วมกับคำสั่ง yield
• การรับส่งค่าใช้ get/set ก็ได้ หรือจะส่งกลับที่ตัว method เองก็ได้ (ในตัวอย่างเป็นแบบหลัง)
• ใช้ร่วมกับ foreach เพื่อวนรอบอ่านค่า object ใน collection
• ในหนึ่งคลาสมีหลาย Iterator ได้
• return type ต้องเป็น IEnumerable หรือ IEnumerator

ตอนต่อไป: Regex (Regular Expression)

Advertisements

ตอน 27 Interface

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

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

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

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

 

นี่คือตอนสุดท้ายในหัวข้อ Inheritance และสิ่งที่ต่อเนื่องจาก Inheritance ในภาษา C#

 

Interface

ผู้เรียนภาษา C# (และภาษา Java ด้วย) บางคนพอได้ยินคำว่า Interface แล้วขวัญบิน เพราะคิดว่าเป็นศาสตร์การเขียนโค้ดระดับสูง คงมีแต่ศาสตราจารย์ทางคอมพิวเตอร์เท่านั้นที่จะเข้าใจได้ นั่นเป็นความเข้าใจผิด อันที่จริง interface เป็นเรื่องเรียบง่ายที่แม้แต่แม่บ้านทุกคนก็สามารถทำความเข้าใจได้

ลองคิดเปรียบเทียบว่าคลาสเป็นเครื่องใช้ไฟฟ้าในครัวเรือน เครื่องใช้ไฟฟ้าก่อนจะทำงานได้ต้องเสียบปลั๊ก ยกตัวอย่างเช่น เครื่องซักผ้ากับเครื่องดูดฝุ่น แม้จะแตกต่างกันมาก แต่ก็เป็นเครื่องใช้ไฟฟ้าเหมือนกัน จึงต้องเสียบปลั๊กเหมือนกัน เครื่องใช้ไฟฟ้าไม่ว่าจะเป็นชนิดก็ตาม ต้องมีปลั๊กที่ตรงตามข้อกำหนด มิฉะนั้นจะเสียบกับปลั๊กที่ฝาผนังไม่ได้ ดังเช่นที่ท่านอาจพบว่าเครื่องใช้ไฟฟ้าที่ซื้อมาจากประเทศอังกฤษไม่สามารถเสียบกับปลั๊กในประเทศไทยได้

ข้อกำหนดของปลั๊กไฟนี้ เทียบได้กับ interface มันคือข้อกำหนดซึ่งทำหน้าที่ระบุเพียงว่าปลั๊กไฟต้องมีลักษณะเช่นใด (คือมีขนาดเท่าใด มีกี่ขา แต่ละขามีขนาดเท่าใดเป็นต้น) แต่ไม่ได้กำหนดว่าจะเครื่องใช้ไฟฟ้านั้นๆ นำไฟฟ้าไปใช้อย่างไร ยกตัวอย่างเช่น เครื่องซักผ้ากับเครื่องดูดฝุ่น แม้จะมีปลั๊กไฟที่ตรงตามข้อกำหนดทั้งคู่ แต่วิธีที่มันนำไฟฟ้าไปประยุกต์ใช้นั้นแตกต่างกันมาก

โปรดคิดเปรียบเทียบว่า ไฟฟ้าคือคลาสหลัก หรือตัวโปรแกรมหลัก เครื่องใช้ไฟฟ้าเปรียบเหมือนคลาสบริการ คลาสบริการจะต้องต่อเชื่อมกับคลาสหลักได้ ข้อกำหนด หรือข้อตกลงของการเชื่อมต่อระหว่างคลาสหลักกับคลาสบริการคือ interface ยกตัวอย่างเช่นเรามีคลาสหลักทำหน้าที่แสดงรายงาน และมีคลาสบริการหลายคลาส ทำหน้าที่เรียงข้อมูล คลาสบริการแต่ละคลาสใช้วิธีเรียงข้อมูลแตกต่างกัน (เช่น quick sort, bubble sort, merge sort และ radix sort) interface คือข้อกำหนดที่ทุกคลาสบริการต้องทำตาม เพื่อให้คลาสหลักสามารถเรียกคลาสบริการเพื่อการเรียงข้อมูลได้ โดยคลาสหลักไม่จำเป็นต้องสนใจว่าคลาสบริการมี method ชื่ออะไร พารามิเตอร์เป็นเช่นไร หรือแม้แต่รับ-ส่งข้อมูลเป็น type อะไร

ภาษา C# สนับสนุนแนวคิด component based programming อย่างเต็มที่ เพราะแนวคิดนี้ช่วยให้การสร้างซอฟท์แวร์มีประสิทธิภาพ ท่านคงยังจำได้จากที่อ่านไปแล้วในบทที่ 1 ว่า แนวคิดนี้เหมือนการต่อ เลโก้ หรือการนำดิจิตอลไอซี มาต่อกัน หากออกแบบการเชื่อมต่อไว้ให้ดี จะทำให้สามารถนำชิ้นส่วนเล็กๆ หลายๆ แบบมาต่อกันให้กลายเป็นระบบใหญ่ที่มีความซับซ้อนได้โดยง่าย ในภาษา C# การเชื่อมต่อนี้คือ interface

หลักการ OOP มีเป้าหมายให้การสร้างซอฟท์แวร์มีความยืดหยุ่น นำกลับมาใช้ใหม่ได้ และบำรุงรักษาได้ง่าย แต่กลไก OOP อื่นๆ ในภาษา C# ช่วยให้บรรลุเป้าหมายนี้ได้เพียงแค่ครึ่งเดียว และ interface เติมเต็มส่วนที่เหลือ ประโยชน์ของ interface มีสามประการดังนี้

• จัดกลุ่มงานที่สัมพันธ์กัน
• ความสะดวกเรื่อง type
• multiple inheritance

จัดกลุ่มงานที่สัมพันธ์กัน: เราใช้ interface เพื่อรวบรวมสิ่งต่างๆ ที่หลายคลาสใช้ร่วมกัน โดยคลาสเหล่านั้นไม่จำเป็นต้องมีความสัมพันธ์กัน (คือไม่สืบสันดานกัน) ยกตัวอย่างเช่นคลาสเครื่องซักผ้า และคลาสเครื่องดูดฝุ่น แม้จะมีความสามารถในการปรับความเร็วในการทำงาน ทั้งคู่ แต่การนำสองคลาสนี้มาเป็น derived class ของคลาส “ปรับความเร็ว” ย่อมไม่สมเหตุสมผล (เพราะการปรับความเร็วของเครื่องซักผ้าและคลาสเครื่องดูดฝุ่นมีความแตกต่างกันมากเกินไป) ที่ถูกคือทั้งคู่ควรเป็น derived class ของคลาส “เครื่องใช้ไฟฟ้า” แล้วจัดให้มี interface การ “ปรับความเร็ว” ที่คลาสทั้งสองใช้ร่วมกัน (แต่มีวิธีปฏิบัติเป็นของตนเอง)

ความสะดวกเรื่อง type: นอกจากนั้น interface ยังช่วยให้เราใช้ object ได้ โดยไม่จำเป็นต้องรู้ type ของมัน ยกตัวอย่างเช่นหากเรารู้สึกว่าความเร็วในการทำงานสูงเกินไป เราสามารถสั่งให้ผู้ที่กำลังทำงานลดความเร็วลงได้ โดยเราไม่จำเป็นต้องรู้ว่าผู้ที่กำลังทำงานเป็น “เครื่องซักผ้า” หรือ “เครื่องดูดฝุ่น” ขอยกอีกตัวอย่างที่เป็นเรื่องของการเขียนโค้ดจริงๆ คืออัลกอริทึมเพื่อเรียงข้อมูล (sorting algorithm) ที่ตามปรกติจะรับ object ที่มี type เป็น interface แบบ IComparable ทำให้มันสามารถจัดเรียงข้อมูลได้ โดยไม่จำเป็นต้องสนใจว่า object ที่รับมามี type อะไร

multiple inheritances: ภาษา C# ไม่สนับสนุนการทำ multiple inheritance (การสืบสันดานจากหลาย base class) เพราะอาจทำให้เกิดข้อผิดพลาดที่ดีบักยาก งานใดที่จำเป็นต้องอาศัย multiple inheritance สามารถทำได้โดยใช้ interface

โปรดดูตัวอย่างโค้ดแสดงการทำงานของ interface ต่อไปนี้

interface I1
{
    void MyFunction();
}
interface I2
{
    void MyFunction();
} 

โค้ดข้างบนคือนิยาม interface class สอง class ชื่อ I1 และ I2 ตามลำดับ โปรดสังเกตว่า interface class คล้าย abstract class ในแง่ที่ว่า ภายในคลาสมีนิยามสมาชิกเฉพาะส่วนหัวเท่านั้น ไม่มีโค้ดที่ทำงานจริง

class Test : I1, I2
{
    public void MyFunction()
    {
        Console.WriteLine("This is I1");
    }
    void I2.MyFunction()
    {
        Console.WriteLine("This is I2");
    }
}

โค้ดข้างบนคือนิยามคลาสธรรมดาชื่อ Test เป็นคลาสที่สืบสันดานจาก interface class สองคลาส (multiple inheritances) ภายในคลาส Test มีโค้ดทำงานของ method ที่รับมรดกมาจากการ interface คือ method MyFunction() โดยมีทั้ง MyFunction() ของ I1 และ MyFunction() ของ I2

    class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test();
            I1 i1 = (I1)t;
            i1.MyFunction();
            I2 i2 = (I2)t;
            i2.MyFunction();
        } 
    }

โค้ดข้างบนคือโค้ดที่สาทิตการใช้งาน interface class I1, I2 และคลาสที่สืบสันดานจากสองคลาสนี้ (คือ Test) ขั้นแรกเราสร้างตัวแปร object ชื่อ t ให้มี type เป็น test และสร้าง instance ของคลาส test จากนั้นสร้างตัวแปร object ชื่อ i1 มี type เป็น I1 และสร้าง instance ของคลาส I1 โดยใช้การ casting จาก type test (คำสั่ง I1 i1 = (I1)t;) บรรทัดถัดมาลองเรียกใช้ method MyFunction(); ดูท่านคิดว่าจะเห็นข้อความว่าอะไร

คำสั่งสองบรรทัดสุดท้ายสร้างตัวแปร object ชื่อ i2 ให้มี type เป็น I2 และสร้าง instance ของคลาส I2 โดยอาศัยการ casting จาก type test (คำสั่ง I2 i2 = (I2)t;) บรรทัดถัดมาลองเรียกใช้ method MyFunction(); ดูท่านคิดว่าจะเห็นข้อความว่าอะไร

ผลลัพธ์จากการรันโค้ดตัวอย่างนี้คือ

This is I1
This is I2

 

เมื่อใดจึงควรใช้ Interface

ถ้าท่านเขียนโปรแกรมเล็กๆ ไว้ใช้เองคนเดียว และเขียนโค้ดด้วยตนเองทั้งหมด คงไม่จำเป็นต้องใช้ interface แต่ถ้าเขียนโปรแกรมซับซ้อนขึ้น หรือทำงานเกี่ยวข้องกับหลายฝ่าย หลายหน่วยงาน บางครั้งการใช้ interface ก็เป็นสิ่งจำเป็น หรือเป็นสิ่งที่ช่วยให้เขียนโปรแกรมได้กระชับขึ้น หรือมีมาตรฐานดีขึ้น หากเป็นซอฟท์แวร์ระบบงานขนาดใหญ่ ที่เขียนโดยนักเขียนโค้ดหลายกลุ่ม ผู้ควบคุมงานหรือผู้ออกแบบซอฟท์แวร์อาจใช้ interface เป็นเครื่องมือช่วยควบคุมการเขียนโค้ดของแต่ละฝ่ายให้ประสานกันได้

ต่อไปลองพิจารณาเหตุการณ์ต่อไปนี้ เราเขียนคลาสหลักคลาสหนึ่ง (สมมุติว่าชื่อคลาส BackUp) ทำหน้าที่เก็บสำรองข้อมูล โดยมีจุดหนึ่งในคลาส BackUp ที่เราต้องการบีบอัดข้อมูลก่อนนำไปเก็บ สมมุติว่าเราสร้างคลาสบริการชื่อ MakeZip ทำหน้าที่บีบอัดข้อมูลแบบ zip เมื่อต้องการบีบข้อมูลเราจึงใช้คำสั่งดังนี้

    MakeZip myZip = new MakeZip();
    myZip.Compress(data);

เมื่อทำงานไปได้ระยะหนึ่งเราต้องการบีบข้อมูลโดยใช้อัลกอริทึมแบบ rar เราจึงต้องสร้างคลาสบริการอีกคลาสหนึ่งชื่อ MakeRar ทำหน้าที่บีบอัดข้อมูลแบบ rar เมื่อต้องการบีบข้อมูลเราจึงใช้คำสั่งดังนี้

MakeRar myRar = new MakeRar()
myRar.Compress(data); 

เมื่อทำงานไปได้ระยะหนึ่งเราต้องการบีบข้อมูลโดยใช้อัลกอริทึมใหม่ๆ เพิ่มอีกห้าแบบคือ cab, tar, arj, gz และ arc เราจึงต้องสร้างคลาสบริการอีกห้าคลาส ทำให้เรามีคลาสบริการที่ทำหน้าที่บีบข้อมูลเจ็ดคลาสที่เวลาเรียกใช้ต้องสร้าง object เจ็ด object และเรียก method ของมันที่แตกต่างกันไปเจ็ดแบบซึ่งไม่ค่อยสะดวกนัก ในกรณีที่มีนักเขียนโค้ดหลายคน แบ่งงานกันนิยามคลาสเหล่านี้ นักเขียนโค้ดแต่ละคนย่อมนิยามวิธีเรียกใช้ method แตกต่างกันไป เพราะไม่มีสิ่งใดทำหน้าที่กำหนดมาตรฐานให้ตรงกัน

หากเราทำงานที่ยกตัวอย่างข้างบนโดยการใช้ interface จะได้ผลลัพธ์ที่สม่ำเสมอกว่า เพราะเราสามารถสร้าง object เพียงตัวเดียวที่เรียกใช้ method การบีบข้อมูลของทุกคลาสบริการได้โดยไม่ต้องเปลี่ยนแปลงวิธีเรียกเลย วิธีทำคือให้นิยาม interface ขึ้นก่อน จากนั้นกำหนดให้คลาสบริการทั้งเจ็ดคลาสทำตามข้อตกลงที่ระบุไว้โดย interface โปรดพิจารณาโค้ดตัวอย่างต่อไปนี้

interface ICompress
{
    string Compress(string data);
}
class MakeZip : ICompress
{
    public string Compress(string data)
 {
      return data + " compressed using Zip"
 }
}
class MakeRar : ICompress
{
    public string Compress(string data)
 {
      return data + " compressed using Rar"
 }
}
class Program
{
    static void Main(string[] args)
    {
        ICompress myCompress = new MakeZip();
        Console.WriteLine(myCompress.Compress("data"));
        Console.ReadLine();
        myCompress = new MakeRar();
        Console.WriteLine(myCompress.Compress("data"));
        Console.ReadLine();
    }
} 

ผลลัพธ์ของการทำงานคือ

Data compressed using Zip
Data compressed using Rar

จากโค้ดข้างบนจะเห็นว่าผู้เขียนนิยาม interface ชื่อ ICompress เพื่อเป็นข้อกำหนดให้คลาสบริการ (คลาสทำหน้าที่บีบข้อมูล) ทุกตัวดำเนินการตาม ในตัวอย่างนี้มีสมาชิกเพียงตัวเดียวคือ method Compress (แต่ในการใช้งานจริงปรกติแล้วจะซับซ้อนกว่านี้มาก) คลาส MakeZip และคลาส MakeRar นำ ICompress ไปใช้ราวกับว่ามันคือ base class แบบ abstract แล้วคลาสบริการทุกคลาสต้องนิยามโค้ดการทำงานให้แก่ method Compress

คลาส Program ทำหน้าที่ทดสอบการทำงานโดยสร้าง object ชื่อ myCompress จากคลาส MakeZip โดยระบุให้มี type เป็น ICompress จากนั้นเรียก method Compress ซึ่งมีผลให้ method ชื่อเดียวกันในคลาส MakeZip ทำงาน บรรทัดต่อมาเปลี่ยน myCompress ให้อ้างอิงไปยัง object ตัวใหม่ก็สามารถทำได้ เพราะถือว่าทั้ง MakeZip และ MakeRar มี type เป็น ICompress เหมือนกัน และการเรียกใช้ method Compress ก็ยังคงมีวิธีใช้คงเดิม เพราะทั้ง MakeZip และ MakeRar ถูกสร้างขึ้นตามข้อตกลงเดียวกัน

 

 

ตัวอย่างงานที่มีคลาสบีบข้อมูลหลายแบบ (แต่ละแบบใช้อัลกอริทึมบีบข้อมูลแตกต่างกัน) หากคลาสเหล่านี้ถูกเขียนขึ้นโดยนักเขียนโค้ดหลายคน การนิยามวิธีเรียกใช้ method และ data type ย่อมแตกต่างกันออกไปตามคตินิยมของนักเขียนโค้ดแต่ละคน หาเรานิยาม interface ไว้ก่อน แล้วให้นักเขียนโค้ดทุกคนดำเนินการตามข้อตกลงใน interface ผู้นิยามคลาสที่ต้องการบีบข้อมูลจะสามารถเรียกใช้ method บีบข้อมูลได้โดยไม่ต้องกังวลถึงวิธีเรียกใช้

 

ความแตกต่างระหว่า interface กับ abstract class

ท่านคงจำได้จากเรื่อง abstract class ที่เรียนไปแล้วในหัวข้อก่อนหน้านี้ในบทนี้ ว่า abstract class อาจมีแต่สมาชิกแบบ abstract ซึ่งไม่มีโค้ดนิยามการทำงาน สิ่งนี้ทำให้มันคล้าย interface แต่หน้าที่และประโยชน์ของ interface กับ abstract class ย่อมไม่เหมือนกัน interface และ abstract class แตกต่างกันดังนี้

 

  • เราทำ multiple inheritance ต่อ Interface ได้ แต่ทำต่อ abstract class ไม่ได้
  • สมาชิกทุกตัวของ interface ต้องไม่มีนิยามการทำงาน แต่ abstract class สามารถมีได้ (คือ concrete method)
  • Interface มี constant ได้เฉพาะแบบ static final ส่วน abstract class มีได้ทั้งที่เป็นแบบ instance (แบบธรรมดา) และที่เป็น static
  • เราใช้ interface เพื่อจัดกลุ่มคุณสมบัติย่อยๆ ที่มีร่วมกันในหลายคลาส โดยคลาสเหล่านั้นไม่ได้ inherit จาก base class เดียวกัน แต่เราใช้ abstract class เพื่อสร้าง base class
  • Interface ทำงานช้ากว่า abstract class
  • หากเราเพิ่ม method ใน interface เราต้องแก้ไขคลาสที่ใช้งาน interface ทุกตัว แต่ถ้าเราเพิ่ม method แบบ virtual ใน abstract class เราไม่จำเป็นต้องแก้ derived class แต่อย่างใด

 

Polymorphism โดยใช้ Interface

ท่านคงยังจำได้จากที่เรียนไปแล้วในบทที่ 1 ว่า polymorphism เกี่ยวข้องกับการเปลี่ยนแปลง method ของ base class โดยการทำ method overriding เพื่อให้ method มีการทำงานที่แตกต่างไปใน derived class ซึ่งถือว่าเป็นการทำ polymorphism แบบที่พบเห็นได้ทั่วไป เรียกว่า “การทำ polymorphism ทาง inheritance”

นอกจากนั้นในบทที่ 4 ท่านได้เรียนการทำ method overloading ซึ่งเป็นการนิยาม method ชื่อเดียวกันหลายๆ แบบ โดยมี signature แตกต่างกัน ทำให้ method ชื่อเดียวสามารถทำงานกับข้อมูล หรือ type ได้หลายแบบ ซึ่งถือว่าเป็นการทำ polymorphism แบบที่เรียกว่า “การทำ polymorphism ขณะ compile-time”

ในบทนี้ จากโค้ดตัวอย่างข้างบน ท่านจะเห็นว่าตัวแปรอ้างอิง myCompress ซึ่งทำหน้าที่อ้างอิง interface type ICompress มีผลให้ตัวแปรนี้ใช้อ้างอิง object ที่ถูกสร้างจากคลาส MakeZip หรือ MakeRar ก็ได้ สลับกลับไปกลับมาก็ได้ ทำให้ method ที่ตัวแปรนี้อ้างถึงเปลี่ยนไปได้ด้วย ยกตัวอย่างเช่น ขณะที่ myCompress อ้างถึง MakeZip เมื่อเราใช้คำสั่ง myCompress.Compress() method Compress จะทำงานแตกต่างจากเมื่อ myCompress อ้างถึง MakeRar การที่ method ชื่อเดียวกัน มี signature เหมือกัน แต่เปลี่ยนแปลงการทำงานไป-มาได้เช่นนี้ ถือว่าเป็น polymorphism ด้วยเช่นกัน เรียกว่า “การทำ polymorphism ผ่านทาง interface”

 

ข้อสรุปเรื่อง interface

  • interface คือข้อตกลง หรือสัญญา หรือข้อกำหนดกลาง ที่หลายๆ คลาสนำไปใช้ร่วมกัน
  • interface ทำให้เกิดความสม่ำเสมอในการเชื่อมต่อระหว่างคลาส
  • interface คือการสนับสนุนแนวคิด component base programming
  • interface รวมกลุ่มคุณสมบัติที่มีร่วมกันในหลายๆ คลาส โดยที่คลาสเหล่านั้นไม่ได้ inherit จาก base class เดียวกัน
  • interface ทำให้เราปฏิบัติต่อกลุ่มของ object ได้ราวกับว่าพวกมันมี type เดียวกัน
  • ตัว interface เองก็คือคลาส
  • interface คล้ายคลาสแบบ abstract
  • เราใช้ interface แทน abstract เพื่อทดแทนการทำ multiple inheritance
  • คลาสที่ไม่เป็น abstract ซึ่งสืบสันดานจาก interface ต้องใส่โค้ดทำงานให้สมาชิกทุกตัว
  • นำ interface ไปสร้าง object โดยตรงไม่ได้
  • ภายใน interface มีสมาชิกต่อไปนี้ได้ methods, properties, indexers, events
  • method ใน interface มีโค้ดทำงานไม่ได้ (มีได้เพียงประกาศส่วนหัว จะมีโค้ดที่เป็นไส้ในไม่ได้)
  • คลาส และ struc อาจสืบสันดานจาก interface ได้มากกว่าหนึ่งคลาส
  • ตัว interface เองก็อาจสืบสันดานจาก interface ได้มากกว่าหนึ่งคลาสเช่นกัน
  • การใช้งาน interface อาจทำให้เกิดภาวะ polymorphism ได้

ตอนต่อไป: IEnumerator และ Indexer

ตอน 26 Method Overrding, name biding การทำ hiding และ Abstract

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

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

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

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

 

นี่คือตอน 3 ของหัวข้อ Inheritance และสิ่งที่ต่อเนื่องจาก Inheritance ในภาษา C#

 

method overriding

เมื่อรับ method เป็นมรดกจาก base class แล้วเราอาจพบว่าบาง method มีคุณสมบัติที่ไม่ตรงกับความต้องการ หากเป็นเช่นนั้นเราสามารถ override method นั้นเพื่อให้กลายเป็น method ที่มีคุณสมบัติตามความต้องการของเราได้

เฉพาะ method ใน base class ที่ประกาศไว้เป็นแบบ virtual เท่านั้นที่จะ override ได้ โปรดพิจารณาตัวอย่างโค้ดต่อไปนี้

1      class A
2      {
3           public void Foo() { }
4           public virtual void Goo() { }
5      }
6      class B : A
7      {
8           public void Foo() { }
9           public void Goo() { }
10          public override void Goo()
11          {
12              base.Goo();
13          }
14     }

โค้ดข้างบนคือนิยามคลาสสองคลาส คลาสแรกชื่อ A ภายในมี method สมาชิกสอง method อันแรกชื่อ Foo() อีก method ชื่อ Goo() ซึ่งถูกประกาศไว้ให้เป็น virtual อีกคลาสหนึ่งชื่อ B สืบสันดานจากคลาส A ภายในคลาสนี้มีนิยาม method สมาชิกสาม method คือ Foo(), Goo() และ Goo() แบบ override ตามลำดับ

 

ข้อสรุปเรื่อง method override

  • method ต้องเป็น virtual จึงจะถูก override ได้
  • method Foo() ของคลาส A (ต่อไปจะเรียก A.Foo())จะถูก override ไม่ได้เพราะไม่เป็น virtual
  • method Goo() ของคลาส A (ต่อไปจะเรียก A.Goo())จะถูก override ได้เพราะเป็น virtual (บรรทัดที่ 4)
  • การทำให้ method ใดๆ กลายเป็นแบบ virtual ทำได้โดยใส่ keyword virtual ไว้หน้า return type ของ method นั้นๆ
  • บรรทัดที่ 8 มีนิยาม method Foo() (ต่อไปจะเรียก B.Foo()) ซ้ำกับ A.Foo()ซึ่งไม่ดี เพราะจะบังนิยามของ A.Foo() ทำให้ A.Foo() ถูกบังไว้ เมื่อคอมไพล์แม้จะไม่ error แต่จะมี warning ว่านิยาม B.Foo() ซ้ำกับใน A.Foo()แก้ไขได้โดยใส่คำสั่ง new ไว้หน้า B.Foo() ซึ่งจะมีผลให้ A.Foo() ถูกบังการทำงานไป (ไม่ทำงาน)
  • บรรทัดที่ 9 อย่างนี้เมื่อคอมไพล์จะเกิด error เพราะถือเป็นการนิยาม method ใหม่เพื่อบัง A.Goo() แต่ A.Goo() มีภาวะเป็น virtual ซึ่งบังไม่ได้ (override ได้เท่านั้น)
  • บรรทัดที่ 10 เป็นการทำ override ที่ถูกต้อง
  • การนิยาม override method ต้องใช้คำสั่ง override
  • การนิยาม override method ต้องมี signature ตรงกับ method ใน base class
  • การนิยาม override method ต้องมี return type (int, string…) ตรงกับ method ใน base class
  • การนิยาม override method ต้องมี access modifier (public, protected…) ตรงกับ method ใน base class
  • การอ้างถึง method ใน base class ทำได้โดยใช้ keyword base (บรรทัดที่ 12)
  • ไม่สามารถ override method แบบ static ได้

 

Name binding

name binding คือการจับคู่ระหว่าง object กับตัวอ้างอิง ปรกติจะเกิดขึ้นระหว่างการคอมไพล์ แต่เมื่อมีการทำ inheritance ทำให้บางสถานการณ์ name binding ไม่สามารถทำได้เมื่อคอมไพล์

ในเอกสารเกี่ยวกับภาษา C# ท่านจะพบคำว่า static binding (early binding ก็ว่า) และ dynamic binding (late binding ก็ว่า) อยู่เสมอ สองคำนี้เป็นเรื่อง name binding ซึ่งเกี่ยวของกับ inheritance โดยตรง ดังนั้นผู้เขียนจึงจะอธิบายภายใต้หัวของ inheritance นี้

name binding คือการจับคู่ระหว่าง instance ของคลาสกับ reference variable ที่เราสร้างขึ้นเพื่อทำหน้าที่เป็นตัวอ้างอิง ยกตัวอย่างเช่น

A foo = new A();

อย่างนี้คือการนำตัวแปรชื่อ foo (ตัวอ้างอิง) ไปจับคู่กับ instance ที่ถูกสร้างจาก type A หากการจับคู่นี้เกิดก่อนโปรแกรมทำงาน (คือเกิดขณะ compile time) จะเรียกว่า static binding หากการจับคู่นี้เกิดขณะโปรแกรมทำงาน (runtime) จะเรียกว่า dynamic binding โปรดพิจารณาโค้ดต่อไปนี้

class A
{
    public virtual void WhoAreYou() { Console.WriteLine("I am an A"); }
}
class B : A
{
    public override void WhoAreYou() { Console.WriteLine("I am a B"); }
}

โค้ดข้างบนนิยามคลาสสองคลาสๆ A ทำหน้าที่เป็น base class พร้อม method แบบ virtual และ คลาส B ทำหน้าที่เป็น derived class และมี method ที่ override method ชื่อเดียวกันของ A

1 A foo = new A();
2 foo.WhoAreYou(); // ได้ผลลัพธ์เป็น “I am a A”
3
4 A Bar = new B();
5 Bar.WhoAreYou(); // ได้ผลลัพธ์เป็น “I am a B” 

บรรทัดที่ 1 และ 2 สร้าง reference object ชื่อ foo มี type เป็น A เมื่อเรียกให้ method WhoAreYou() ทำงานปรากฏว่า method WhoAreYou()ของคลาส A ทำงาน ในทำนองเดียวกัน บรรทัดที่ 4 และ 5 สร้าง reference object ชื่อ Bar มี type เป็น B เมื่อเรียกให้ method WhoAreYou() ทำงานปรากฏว่า method WhoAreYou()ของคลาส B ทำงาน

1 static void Use(A x)
2 {
3      x.WhoAreYou();
4 }
5 static void Main(string[] args)
6 {
7      Use(new A()); // ได้ผลลัพธ์เป็น “I am a A”
8      Use(new B()); // ได้ผลลัพธ์เป็น “I am a B”
9 }

ตัวอย่างโค้ดข้างบนผู้เขียนสร้างนิยาม method ใหม่ขึ้นมา (ในคลาส ที่ไม่ใช่ A หรือ B) ชื่อ Use รับ argument หนึ่งตัวมี type เป็น A จากนั้นเรียกใช้ method ที่ยังไม่รู้ว่าจะเป็นของคลาส A หรือ B (บรรทัดที่ 3) ในบรรทัดที่ 7 ผู้เขียนเรียก method Use โดยส่งพารามิเตอร์เป็น type A และบรรทัดที่ 8 ผู้เขียนเรียก method Use โดยส่งพารามิเตอร์เป็น type B

จะเห็นว่าโค้ดตัวอย่างนี้ เมื่อมีการเรียกใช้ method ที่ยังไม่รู้ว่าจะเป็นของคลาส A หรือ B (บรรทัดที่ 3) จึงยังไม่สามารถทำ name binding ได้ตอนคอมไพล์ (early binding) ต้องรอไปจนกระทั่งโปรแกรมทำงานที่บรรทัดที่ 7 และ 8 การ binding จึงจะเกิดขึ้นได้ (late binding)

 

ภาษา C# สนับสนุนการทำ late binding ซึ่งจะเกิดขึ้นในกรณีที่ compiler ไม่สามารถหา type ของ object ตอนคอมไพล์ได้ การทำ name binding จึงต้องรอไปทำตอน run-time โดย CLR เมื่อ object ถูกกำหนด type ที่แน่นอนแล้ว

 

ข้อสรุปเรื่อง name binding

  • name binding คือการจับคู่ระหว่าง object กับตัวอ้างอิง
  • หากเกิดตอนคอมไพล์เรียก early binding
  • หากเกิดตอน runtime เรียก late binding
  • late binding จะเกิดเมื่อต้องหาค่านิพจน์ก่อนจึงจะรู้ type ของ object

 

การทำ hiding

เมื่อทำ inheritance แล้วเราสามารถบัง method บาง method ของ base class ได้ ท่านคงจำได้จากหัวข้อ method overriding ว่าหากเรานิยาม method ใน derived class ชื่อเดียวกับ method ใน base class จะมีผลทำให้ method ใน base class ถูกบังไว้ (hiding) หากเราต้องการทำเช่นนั้นโดยเจตนาให้ใช้คำสั่ง new โปรดพิจารณาโค้ดตัวอย่างต่อไปนี้

class A
{
    public int x;
    public void F() { }
    public virtual void G() { }
}
class B : A
{
    public new int x;
    public new void F() { }
    public new void G() { }
} 

โค้ดข้างบนเป็นนิยามคลาส 2 คลาสๆ A ทำหน้าที่เป็น base class และคลาส B ทำหน้าที่เป็น derived class คลาส A มีสมาชิกสามตัว เป็น field หนึ่งตัวที่เหลือเป็น method โดยสมาชิกเหล่านี้ถูกบังไว้ทั้งหมดในคลาส B ต่อไปโปรดพิจารณาการนำ type ทั้งสองไปใช้

B b = new B();         // สร้าง object b จากกคลาส B
b.x = 12;              // ค่าที่ถูกเปลี่ยนคือ B.x
b.F();                 // เรียก method B.F()
b.G();                 // เรียก method B.G()
((A)b).x = 34;         // ค่าที่ถูกเปลี่ยนคือ A.x
((A)b).F();            // เรียก method A.F()
((A)b).G();            // เรียก method A.G()

ข้อสรุปเรื่อง hiding

  • hiding คือการซ่อนสมาชิกของ base class
  • hiding ทำทำได้กับสมาชิกแบบ method, constant, field, properties และ type
  • hiding ทำได้ทั้งในคลาสและ struct
  • method ใน derived class จะบัง properties, field และ type ชื่อเดียวกันใน base class
    ใช้ keyword new และ override พร้อมๆ กันกับสมาชิกตัวเดียวกันไม่ได้

 

Abstract class

ในการทำ inheritance บางครั้งเราต้องการสร้างคลาสเพื่อนำไปใช้เป็น base class โดยเฉพาะ ซึ่งทำได้โดยใช้คำสั่ง abstract

abstract คือ modifier ตัวหนึ่งที่นำไปใช้กับคลาสก็ได้ หรือจะใช้กับ method, properties, indexers และ event ก็ได้เช่นกัน เมื่อเราต้องการสร้างคลาสเพื่อนำไปใช้เป็น base class โดยเฉพาะ (ไม่ใช่คลาสที่มีไว้เพื่อสร้าง instance เป็น object) เราจะใช้ modifier abstract กำกับไว้หน้าชื่อคลาส ซึ่งมีผลให้คลาสนั้นกลายเป็น abstract class

abstract class เป็นคลาสพิเศษ เพราะภายใน abstract class อาจจะมีแต่ประกาศนิยามต่างๆ โดยไม่มีตัวโค้ดจริงๆ (คือมีแต่ส่วนหัว ไม่มีไส้ใน) คลาสที่นำ abstract class ไปใช้ (เพื่อสืบสันดาน คือ derived class) จะต้องใส่ส่วนโค้ดจริงๆ ของนิยามนั้น โปรดพิจารณาโค้ดตัวอย่างต่อไปนี้

    abstract class ShapesClass
    {
        abstract public int Area();
    }

นี่คือนิยาม abstract class ชื่อ ShapesClass ภายในคลาสนี้มีนิยาม method ชื่อ Area() ซึ่งมี modifier เป็น abstract เช่นเดียวกัน โปรดสังเกตว่า method นี้มีแต่ประกาศนิยามส่วนหัว ไม่มีส่วนโค้ดภายใน method แต่อย่างใด

    class Square : ShapesClass
    {
        int x, y;
        public override int Area()
        {
            return x * y;
        }
    }

โค้ดข้างบนเป็นนิยามคลาส Square ซึ่งสืบสันดานจาก ShapsClass โปรดสังเกตว่าคลาส Square จำเป็นต้องมีส่วนนิยามโค้ดของ method Area() มิฉะนั้นจะเกิด compile error

ข้อสรุปเรื่อง abstract class

  • abstract class คือคลาส
  • abstract class มีไว้ทำ base class โดยเฉพาะ
  • ใช้ abstract class สร้าง object ไม่ได้
  • ใน abstract class จะมีสมาชิกที่ไม่ใช่ abstract ก็ได้
  • Method ที่ไม่ใช่ abstract ต้องมีโค้ดนิยามการทำงาน
  • สมาชิกของ abstract class ไม่ได้เป็น abstract ไปโดยปริยาย
  • abstract method ต้องไม่มีส่วนโค้ดสำหรับการทำงาน
  • abstract method มีภาวะเป็น virtual โดยปริยาย
  • abstract method ต้องอยู่ภายใน abstract class เท่านั้น
  • derived class จะทำ override ต่อ method แบบ virtual ที่อยู่ใน abstract base class หรือไม่ก็ได้ 
  • Derived class ไม่จำเป็นต้องกำหนดโค้ดการทำงานให้ method แบบ virtual ของ abstract base class
  • Derived class จำเป็นต้องกำหนดโค้ดการทำงานให้ method แบบ abstract ของ abstract base class ให้ครบทุกตัว

ตอนต่อไป: Interface

ตอน 25 assignment, type casting และคำสั่ง as

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

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

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

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

นี่คือตอน 2 ของหัวข้อ Inheritance และสิ่งที่ต่อเนื่องจาก Inheritance ในภาษา C#

 

assignment, type casting และคำสั่ง as

คำว่า assignment ในที่นี้หมายถึงการนำคลาสไปใช้สร้าง instance หรือการสร้าง object หรือการสร้างตัวแปร (variable) แบบ reference type นั่นเอง

ภาษา C# เป็นภาษาที่มีคุณสมบัติ type safety คือช่วยตรวจสอบความถูกต้อง (type check) ในการสร้างและการใช้งาน type ตลอดเวลาเพื่อป้องการความผิดพลาดที่เรียกว่า type error (เดิมทีนักเขียนโค้ดต้องระวังเอาเอง)

เมื่อมองในด้าน type check เราอาจแบ่ง type ออกได้เป็นสองแบบคือ

  • Static type: คือ type ที่ภาษา C# สามารถทำ type check ได้เลยโดยไม่ต้องหาค่าของมันขณะ runtime เสียก่อน
  • Dynamic type: คือ type ที่ต้องหาค่าตอน runtime ก่อนจึงจะทำ type check ได้

ต่อไปนี้ผู้เขียนจะอธิบายรายละเอียดเรื่อง Static และ Dynamic type โดยยกตัวอย่างโค้ด

การนิยามคลาสคือการสร้าง type ใหม่ขึ้น ดังนั้นหากเรานิยามคลาส 3 คลาสดังนี้

class A {…}
class B:A {…}
class C:B {…}

เมื่อเรานำคลาสไปใช้ดังนี้

  • A foo = new A(); ภาวะ static type ของ foo เกิดจากการกำหนด type เมื่อประกาศ type (ในที่นี้คือ A) และภาวะ dynamic type ของ foo เกิดจาก type ของ object ภายในตัว foo เอง (ในที่นี้คือ A เช่นกัน)
  • foo = new B(); ภาวะ dynamic type ของ foo คือ B
  • foo = new C(); ภาวะ dynamic type ของ foo คือ C
  • B bar = foo; ทำไม่ได้ จะเกิดคอมไพล์ error

เมื่อกำหนดให้ foo = new C(); เมื่อตรวจสอบ type ขณะ runtime จะได้ผลดังนี้

  • if(foo is C) ได้ผลลัพธ์เป็น true
  • if(foo is B) ได้ผลลัพธ์เป็น true เช่นเดียวกัน
  • if(foo is A) ได้ผลลัพธ์เป็น true แต่จะมี warning

เมื่อกำหนดให้ foo = null;

  • if(foo is C) ได้ผลลัพธ์เป็น false

ข้อสรุปเรื่องassignment

  • assignment คือการสร้าง object
  • assignment คือการสร้าง instance จากคลาสหรือ struct
  • assignment คือการสร้างตัวแปร object
  • assignment คือการสร้าง reference variable
  • เมื่อทำ assignment ภาษา C# จะทำ type check ให้โดยอัตโนมัติ

 

type casting

type casting คือการแปลง type จากแบบหนึ่งไปเป็นอีกแบบหนึ่ง เราสามารถแปลง type กลับไปกลับมาระหว่าง class ที่สืบสันดานกันได้ ยกตัวอย่างเช่น

เมื่อกำหนดให้ A foo = new C(); แล้วแปลง type โดยการ casting ผลที่ได้จะเป็นดังนี้

  • B bar = (B)foo; นี่คือการแปลง type ของ foo (เดิมเป็น C) ให้กลายเป็น type B สามารถทำได้เพราะ C เป็น subclass (derived class) ของ B
    เมื่อกำหนดให้ foo = null; แล้วทำ casting โดย
  • c = (C)foo ก็ทำได้เช่นเดียวกันเพราะเราสามารถ cast ให้ null เป็น reference type อะไรก็ได้

ข้อสรุปเรื่อง casting

  • casting คือการแปลง type
  • casting จาก null เป็น type อะไรก็ได้
  • casting ทำได้เฉพาะ type ที่อยู่ในตระกูลเดียวกัน (เช่น int เป็น double ได้)
  • casting ระหว่าง type คนละตระกูลไม่ได้ (เช่น string เป็น int ไม่ได้)

 

กรรมกรซอท์แวร์ชื่อ Emilio Guijarro ทดลองทำ casting แบบ prefix (คือแบบใส่วงเล็บไว้หน้า)เปรียบเทียบกับการทำ casting โดยใช้คำสั่ง as พบว่าการทำ casting โดยใช้คำสั่ง as ทำงานได้เร็วกว่ามาก ดังจะเห็นได้จากผลลัพธ์ของการทดสอบในแผนภูมิ (โปรดอ่านรายละเอียดในบทความของเขาที่ www.codeproject.com/csharp/csharpcasts.asp)

 

 

คำสั่ง as

คำสั่ง as ทำหน้าที่แปลง type จากแบบหนึ่งไปเป็นอีกแบบหนึ่งเหมือนการ casting ยกเว้นว่าการแปลงด้วย casting หากล้มเหลวเพราะ type ที่แปลงไม่เข้ากัน จะเกิด error แบบ exception ขึ้น แต่การใช้ as จะไม่เกิด exception แต่จะได้ผลลัพธ์เป็น null แทน ลองดูตัวอย่างโค้ดต่อไปนี้

  • เมื่อกำหนดให้ A foo = new C();
  • B bar = foo as B; แปลง type ของ foo จาก A เป็น B ทำได้ ไม่ error
  • C c = foo as C; แปลง type ของ foo จาก B เป็น C ทำได้ ไม่ error

เมื่อกำหนดให้ foo = null;

  • c = foo as C; แปลง type ของ foo จาก null เป็น C ทำไม่ได้ กลายเป็น null ตามเดิม

สรุปเรื่องคำสั่ง as

  • as คือการแปลง type
  • as จาก null เป็น type อะไรก็ได้
  • as ทำได้เฉพาะ type ที่อยู่ในตระกูลเดียวกัน (เช่น int เป็น double ได้)
  • as ระหว่าง type คนละตระกูล (เช่น string เป็น int) จะได้ผลลัพธ์เป็น null

ตอนต่อไป: method overriding, Name binding, การทำ hiding

ตอน 24 Inheritance และ Versioning ในภาษา C#

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

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

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

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

 

การดีบักยากกว่าการเขียนโค้ดสองเท่า ดังนั้นหากคุณเขียนโค้ดโดยใช้สติปัญญาทั้งหมดที่มี คุณจะโง่เกินกว่าที่จะดีบักมันได้
ไบรอัน ดับบลิว เคอร์นิกาน (บิดาภาษา C)

 

Inheritance และ Versioning ในภาษา C#

 

บทที่ 5 ภาษา C# ระดับก้าวหน้า

บทนี้ผู้เขียนจะอธิบายลักษณะพิเศษ (features) ต่างๆ ของภาษา C# ซึ่งเป็นเครื่องอำนวยให้เราเขียนโค้ดเป็น OOP ได้โดยสะดวกและสง่างาม ขอให้ท่านมอง features เหล่านี้เป็นเหมือนเครื่องมือที่บางอย่างเราต้องใช้บ่อยๆ บางอย่างนานๆ ครั้งจึงจะใช้ และบางอย่างก็ไม่มีโอกาสใช้เลย

ผู้เขียนรวบรวม features ระดับก้าวหน้าของภาษา C# ไว้ครบตั้งแต่ C# เวอร์ชันแรก ถึง c#3.0 แต่เนื่องจากภาษา C# มี features ก้าวหน้าจำนวนมาก จึงนำเสนอได้เพียงโดยย่อพอเป็นนิสัย สำหรับใช้เป็นความรู้เบื้องต้นเพื่อศึกษาในระดับสูงต่อไป

ตัวอย่างโค้ดที่แสดงในบทนี้แม้จะเป็นโค้ดที่สมบูรณ์ ได้รับการทดสอบและคอมไพล์แล้วว่าถูกต้อง นำไปรันได้จริงๆ แต่ก็เป็นโค้ดที่เขียนอย่างสั้นที่สุด เรียบง่ายที่สุด ใช้เพื่ออธิบายแนวความคิด (abstract) เท่านั้น ไม่ใช่โค้ดที่เขียนเพื่อใช้ประยุกต์ใช้งานจริงๆ ส่วนโค้ดที่เขียนเพื่อใช้ประยุกต์ใช้งานจริง ท่านจะพบได้ในภาคปฏิบัติของหนังสือเล่มนี้

 

ในบทนี้ท่านจะได้เรียนเรื่องต่างๆ ต่อไปนี้

• Inheritance
• Versioning
• assignment
• type casting
• คำสั่ง as
• method overriding
• Name binding
• การทำ hiding
• Abstract
• Interface
• IEnumerator
• Indexer
• Iterator, yield
• Regex (Regular Expression)
• Generic
• Operator overloading
• Delegate
• Pointer และ Unsafe Code
• Implicitly typed local variables
• Lambda expressions
• Object และ collection initializes
• Anonymous types
• Implicitly typed arrays
• Query expressions
• Expression trees

 

inheritance

ท่านคงยังจำได้จากที่อ่านไปแล้วในบทที่ 1 ว่า inheritance คือหลักสำคัญอย่างหนึ่งใน OOP แม้หลักการ inheritance จะเรียบง่ายและสั้น อธิบายเพียงหนึ่งหน้าก็กินความครบถ้วน แต่ผลลัพธ์ที่ได้จากการ inheritance นั้นลึกซึ้งกินใจ จึงมีหัวข้อย่อยที่เกี่ยวพันกับ inheritance หลายหัวข้อที่ผู้เขียนจะพูดถึงต่อไปดังนี้

  • Syntax
  • Versioning
  • assignment
  • type casting
  • คำสั่ง as
  • method overriding
  • Name binding
  • การทำ hiding
  • Abstract

เนื่องจากเรื่อง inheritance ในภาษา C# ค่อนข้างยาว ผู้เขียนจึงจะแบ่งออกเป็นสามตอนดังนี้

  • ตอนที่ 1: Systax, Versioning
  • ตอนที่ 2: assignment, type casting, คำสั่ง as
  • ตอนที่ 3: method overriding, name bindgin การทำ hiding, abstract

 

Syntax

วากยสัมพันธ์ (syntax โครงสร้างตามแบบแผนของภาษา) ของ inheritance ในภาษา C# เป็นดังนี้

class A
{
    int a;
    public A() { }
    public void F() { }
}
class B : A
{
    int b;
    public B() { }
    public void G() { }
} 

ตัวอย่างโค้ดข้างบนคือรูปแบบการทำ inheritance ที่สมบูรณ์

ข้อสรุปเรื่อง inheritance

  • โค้ดข้างบนมีคลาสสองคลาส คือ A และ B
  • คลาส A คือ base class (หรือ super class)
  • คลาส B คือ derived class (หรือ sub class, child class)
  • การประกาศ inheritance ทำที่หัวของ derived class โดยใช้ชื่อของ base class ตามด้วยเครื่องหมายจุดคู่ และชื่อของ subclass (A:B)
  • มรดกที่คลาส B ได้รับจาก A คือ field a และ method F()
  • constructor ไม่เป็นมรดก
  • เราสามารถ override method ที่ได้รับมาเป็นมรดก (ไม่แสดงข้างบน)
  • ภาษา C# ให้ทำ inheritance ได้จาก base class เพียงคลาสเดียว
  • คลาสรับมรดกจาก struct ไม่ได้ รับได้จากคลาสเท่านั้น
  • คลาสทุกคลาสในภาษา C# ล้วนเป็น derived class
  • หากไม่ระบุ base class จะถือว่า base class คือ object

 

Versioning

versioning ในหัวข้อนี้เป็น versioning ในแง่มุมของการทำ inheritance เป็นหลักการที่ช่วยให้การพัฒนาซอฟท์แวร์แบบ OOP เป็นไปได้โดยไม่เกิดความขัดแย้ง โดยเฉพาะอย่างยิ่งการพัฒนา base class และ derived class โดยให้ทั้งคู่สามารถถูกแก้ไขเปลี่ยนแปลงเพื่อเพิ่มความสามารถ และเพิ่มประสิทธิภาพได้โดยไม่ขึ้นแก่กัน และไม่จำเป็นต้องอยู่ใน class library เดียวกัน

ยกตัวอย่างเช่นการเพิ่มสมาชิกใหม่ให้แก่ base class แม้จะมีชื่อซ้ำกับสมาชิกของ derived class ก็จะไม่ทำให้เกิด error การจะทำดังนี้ได้ผู้นิยามคลาสจะต้องประกาศให้รู้โดยเปิดเผยว่า method ใดบ้างที่override มาจาก method ที่ได้สืบสันดานมา หรือ method นั้น (แม้จะชื่อตรงกับ method ใน base class) แต่ก็เป็น method ใหม่ที่บัง method ใน base class ไว้

method ในภาษา C# ไม่ได้เป็น virtual โดยอัตโนมัติ (ไม่เหมือน Java) หากต้องการให้ method เป็น virtual (ดูรายละเอียดเรื่อง virtual ในหัวข้อ inheritance) ต้องกำหนด modifier virtual ไว้หน้านิยาม method ของ base class จากนั้น derived class สามารถ override method นั้นได้โดยใช้ keyword override หรือจะเพียงแค่บัง method นั้นก็ได้โดยการใช้ keyword new หน้านิยาม method ชื่อเดียวกันที่นิยามขึ้นใหม่ใน derived class ก็ได้

หากพยายามนิยาม method ใน derived class โดยมีชื่อตรงกับ method ใน base class โดยไม่ใส่ keyword override หรือ new ไว้หน้านิยาม method เมื่อคอมไพล์แม้จะไม่ error แต่จะมี warning ว่า method ใน derived class จะบัง method ใน base class โปรดพิจารณาโค้ดต่อไปนี้

public class MyBase
{
    public virtual string Meth1()
    {
        return "MyBase-Meth1";
    }
    public virtual string Meth2()
    {
        return "MyBase-Meth2";
    }
    public virtual string Meth3()
    {
        return "MyBase-Meth3";
    }
}

นี่คือนิยามคลาสที่เราจะใช้เป็น base class ในคลาสนี้มี method สาม method โดยเรากำหนดให้ทุก method มี modifier เป็นแบบ virtual ทั้งหมด

1     class MyDerived : MyBase
2     {
3         public override string Meth1()
4         {
5             return "MyDerived-Meth1"
6         }
7         public new string Meth2()
8         {
9             return "MyDerived-Meth2"
10        }
11        public string Meth3()
12        {
13            return "MyDerived-Meth3"
14        }
15    } 

โค้ดตัวอย่างข้างบนคือนิยามคลาสที่สืบสันดานจากคลาส MyBase จากนั้นนิยาม method ทับของเก่าครบทุก method บรรทัดที่ 3-6 นิยาม method Meth1() โดยใช้ keyword override ซึ่งเป็นการ override ตามปรกติ บรรทัดที่ 7-10 นิยาม method Meth2() โดยใช้ keyword new ซึ่งเป็นการทำ method hiding ตามปรกติ และบรรทัดที่ 11-15 นิยาม method Meth3() โดยไม่ได้ใช้ keyword override หรือ new ซึ่งเป็นการ override ที่ผิดปรกติ แม้เมื่อคอมไพล์แม้จะไม่ error แต่จะมี warning ว่า method Meth3() ใน derived class จะบัง method Meth3() ใน base class

จุดสำคัญเรื่อง versioning คือเมื่อเวลาผ่านไป หากผู้นิยาม derive class เพิ่มนิยาม method ดังนี้

class Base { }
class Derived : Base
{
    public void F() { }
} 

และต่อมาผู้นิยาม base class (ซึ่งไม่รู้ว่าผู้นิยาม derived class ได้เพิ่มนิยาม method ใดๆ อะไรไปบ้างหรือไม่) ได้เพิ่ม method ใน base class ของตนดังนี้

class Base
{
    public void F() { }
}
class Derived : Base
{
    public void F() { }
} 

ซึ่ง method นี้มีชื่อซ้ำกันกับ method ที่ผู้นิยาม derived class ได้เพิ่มไปก่อนหน้านั้นแล้ว สภาพการณ์อย่างนี้ไม่ก่อให้เกิดความขัดแย้งหรือความยุ่งยากใดๆ เพราะภาษา C# ยอมให้ derived class มี method ชื่อซ้ำกับ base class ได้แม้ base class จะไม่ได้กำหนดให้ method นั้นเป็น virtual ก็ตาม และแม้ derived class จะไม่ใช้ keyword override หรือ new ก็ยังคอมไพล์ผ่านได้ (compiler จะแจ้งเตือนว่ามีนิยาม method ซ้อนทับกัน แต่ไม่ถือว่าเป็นความผิดร้ายแรงจนโปรแกรมไม่อาจทำงานได้)

ตอนต่อไป : assignment, type casting และคำสั่ง as

ตอน 23 ตัวแปร และตัวแปรแบบ pointer ในภาษา C#

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

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

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

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

 

 

ตัวแปร – pointer และ unsafe code

 

ตัวแปร (Variable)

ตัวแปรหรือ variable ในภาษา C# เหมือนตัวแปรในภาษาอื่นๆ ที่มีไว้ใช้เก็บข้อมูลต่างๆ เพื่อการประมวลผล แต่การใช้งานตัวแปรในภาษา C# เมื่อเปรียบเทียบกับภาษาอื่นอย่าง VB6, C, C++, JavaScript และ Java แล้วมีความแตกต่างและเหมือนกันบางประการดังนี้

 

  • ในภาษา C# ไม่มีตัวแปรโดดๆ ที่อยู่อย่างลอยๆ (อย่างในภาษา VB6 และ JavaScript) ตัวแปรทุกตัวต้องเป็นสมาชิกของคลาส หรือ struct หรือเป็นตัวแปรท้องถิ่นภายใน method
  • ตัวแปรในภาษา C# มีลักษณะ type safe (หรือ strong type ตรงข้ามกับ weak type ในภาษาอื่น เช่นภาษา JavaScript) คือเคร่งครัดใน type ทำให้เมื่อประกาศตัวแปรแล้ว เราจะไม่สามารถเปลี่ยน type ของตัวแปรได้อีก
  • ตัวแปรที่เป็นสมาชิกของคลาส (หรือ struct) จะเรียกว่า field (เหมือนภาษา Java)
  • ตัวแปรที่ประกาศไว้ภายใน method เรียกว่า local variable หรือตัวแปรท้องถิ่น
  • Field จะมี scope เป็น global ภายในคลาส (หรือ struct) คือโค้ดทุกแห่งในคลาสสามารถเข้าถึงตัวแปรนั้นๆ ได้
  • ตัวแปรจะมี scope อยู่ภายใน คลาส (หรือ struct) ที่ประกาศมันเท่านั้น
  • ตัวแปรที่ถูกประกาศไว้ภายใต้ block ใดๆ จะมีภาวะเป็นตัวแปรท้องถิ่นของ block นั้น และมี scope อยู่เฉพาะใน block นั้นๆ ยกตัวอย่างเช่น หากเราประกาศตัวแปรชื่อ a ไว้ภายใน block ของ for ตัวแปร a จะมี scope อยู่เฉพาะใน loop for นั้นเท่านั้น
  • ตัวแปรอาจเป็น reference type และ value type ก็ได้
  • Derived class รับมรดกตัวแปรแบบ filed ของ base class ได้
  • ภาษา C# มีตัวแปรแบบ pointer เช่นเดียวกับภาษา C (เฉพาะใน unsafe code)
  • เมื่อคอมไพล์โปรแกรมแล้ว ชื่อของตัวแปรจะถูกนำไปเก็บไว้ใน assembly ในลักษณะของ metadata

โปรดพิจารณาตัวอย่างโค้ดต่อไปนี้

int a = 10; 

โค้ดข้างบนคือการประกาศตัวแปรชื่อ a ให้มี type เป็น int และกำหนดค่าเริ่มต้นเป็น 10 เมื่อคำสั่งบรรทัดนี้ทำงาน CLR จะสร้างที่เก็บข้อมูล (ใน heap หากตัวแปรนี้เป็น filed หรือใน stack หากตัวแปรนี้เป็นตัวแปรท้องถิ่นของ method) แล้วใช้ a เป็นตัวอ้างอิง (ซึ่งทำหน้าที่เหมือน pointer)ชี้ไปยังที่เก็บข้อมูลนั้น

a = a + 5;

โค้ดบรรทัดนี้หากกล่าวอย่างมโนคติจะพูดว่า “นำค่าที่เก็บอยู่ในตัวแปร a มาบวกกับเลข 5 แล้วนำไปเก็บในตัวแปร a” แต่ถ้าจะกล่าวให้ถูกต้องตามความจริงจะต้องพูดว่า “นำเลข 5 ไปบวกกับค่าที่ถูกชี้โดย a แล้วนำผลลัพธ์ที่ได้ไปเก็บในที่ซึ่งชี้โดย a”

จะเห็นว่าตัวแปร a ทำหน้าที่อ้างอิงไปยังค่า (ซึ่งตอนแรกเป็น 10 ต่อมาภายหลังกลายเป็น 15) โดยตรง การอ้างไปยังค่าโดยตรงเช่นนี้ ทำให้ตัวแปร a มีภาวะเป็น value type

 

pointer

Pointer คือตัวแปรที่เก็บค่าอ้างอิงไปยังตำแหน่งในหน่วยความจำ (เหมือน indirect addressing mode ในภาษาแอสเซมบลี) ในภาษา C# Pointer ถือเป็น data type แบบที่สาม (สองแบบแรกคือ value type และ reference type ที่ท่านคงจำได้จากในบทที่ 3)

pointer คือตัวแปรที่เราใช้งานได้ภายในบล็อก unsafe เท่านั้น ภาษา C# สนับสนุนการนำ pointer ไปใช้เป็นสมาชิกของคลาส และ struct (ภาษา Java ก็มี pointer แต่เราไม่สามารถเขียนโปรแกรมจัดการกับ pointer ได้โดยตรง)

ภาษา C# สนับสนุนตัวแปร pointer ทุกแบบเช่นเดียวกับภาษา C++ เช่น pointer ธรรมดา pointer ที่ชี้ไปยัง pointer, pointer ที่ชี้ไปยัง array และ pointer ที่ชี้ไปยัง array ของ pointer เป็นต้น data type ที่กำหนดให้ pointer ได้คือ sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, และ bool

ตัวแปรแบบ pointer แตกต่างจากตัวแปรธรรมดา เมื่อเราอ้างถึงชื่อของตัวแปร ชื่อสมาชิกของ structure หรือชื่อสมาชิกของคลาส เท่ากับเราบอกคอมพิวเตอร์ว่า เราต้องการทำบางอย่างกับหน่วยความจำที่ชื่อนั้นเป็นเจ้าของ โปรดพิจารณาตัวอย่างโค้ดต่อไปนี้

char foo = "A";
char oldFoo;
oldFoo = foo; 

ตัวอย่างโค้ดข้างบนเป็นตัวอย่างโปรแกรมที่ใช้ตัวแปรธรรมดา (ที่ไม่ใช่ pointer) บรรทัดแรกสั่งให้คอมพิวเตอร์นำตัวอักษร A ไปเก็บยังหน่วยความจำที่ตัวแปร foo ครอบครองอยู่ บรรทัดต่อมาสร้างตัวแปรชื่อ oldFoo บรรทัดสุดท้ายบอกคอมพิวเตอร์ให้คัดลอกข้อมูลที่เก็บอยู่ภายหน่วยความจำของ foo ไปเก็บไว้ในหน่วยความจำของ oldFoo

ตัวแปรแบบ pointer แตกต่างจากตัวแปรในตัวอย่างข้างบน เพราะสิ่งที่ pointer เก็บคือเลขตำแหน่งหน่วยความจำ ซึ่งโดยปรกติแล้ว จะเป็นเลขตำแหน่งของตัวแปร หรือเลขตำแหน่งของสมาชิกของคลาสหรือ struct

ต่อไปนี้ผู้เขียนจะอธิบายวิธีสร้างและใช้งาน pointer หากท่านเคยเรียน หรือเคยเขียนโปรแกรมภาษา C หรือ C++ ท่านจะพบว่า pointer ในภาษา C# ไม่มีความแตกต่างใดๆ กับ pointer ในสองภาษาที่กล่าวมา ท่านจึงอาจข้ามหัวข้อถัดไปนี้ได้เลย

ภาพนี้แสดงความแตกต่างระหว่างตัวแปรธรรมดากับตัวแปรแบบ pointer คำสั่ง int foo = 567; ทำหน้าที่สร้างตัวแปรธรรมดา (เป็นแบบ value type) เนื่องจาก foo เป็นตัวแปรท้องถิ่นของ method (โค้ดตัวอย่างไม่ได้แสดงการประกาศ method) มันจึงมีที่อยู่ใน stack ในที่นี้สมมุติว่าอยู่ที่เลขตำแหน่งของหน่วยความจำ (memory address) 100 โค้ดบรรทัดที่สองคือ int* bar = &foo; ทำหน้าที่นำ memory address ของตัวแปร foo มาเก็บไว้ในตัวแปร pointer bar ทำให้ bar มีค่าเท่ากับ 100

 

วิธีประกาศ pointer

การสร้างตัวแปรแบบ pointer ก็เหมือนการสร้างตัวแปรธรรมดา แต่มีจุดแตกต่างเล็กน้อย ตัวอย่างข้างล่างแสดงการประกาศตัวแปรแบบ pointer ชื่อ foo

char* foo;

คำสั่งประกาศตัวแปรแบบ pointer มีองค์ประกอบสี่ส่วนดังนี้

• ชนิดข้อมูล: ชนิดข้อมูลของตำแหน่งที่ pointer ชี้
• เครื่องหมายจอกจัน: บอกคอมพิวเตอร์ว่ากำลังประกาศตัวแปร pointer
• ชื่อตัวแปร: ใช้เพื่อแยกความแตกต่างระหว่าง pointer แต่ละตัว และใช้เพื่ออ้างถึง pointer ภายในโปรแกรม
• เครื่องหมายอัฒภาค: (เครื่องหมาย 😉 ใช้บอกให้คอมพิวเตอร์รู้ว่าคำสั่งบรรทัดนี้จบแล้ว

ภาษา C# สนับสนุนการประกาศตัวแปรหลายๆ ตัวในคำสั่งเดียว โดยมีวิธีทำดังนี้

int* p1, p2, p3;      // ถูก
int *p1, *p2, *p3;    // ผิด

นี่คือการประกาศตัวแปรแบบ pointer ที่มี data type เป็น int สามตัว

 

data type และ pointer

เราใช้ data type ในการประกาศตัวแปรธรรมดา บอกให้คอมพิวเตอร์รู้ว่าจะต้องจองเนื้อที่ในหน่วยความจำขนาดเท่าใด และใช้เก็บข้อมูลชนิดใด แต่ตัวแปรแบบ pointer ไม่เป็นอย่างนั้น data type ที่ใช้เมื่อประกาศตัวแปร pointer ทำหน้าที่บอกคอมพิวเตอร์ว่าหน่วยความจำ address ที่มันชี้ มี data type เป็นแบบใด

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

เครื่องหมายดอกจันที่ใช้ในการประกาศ pointer ทำหน้าที่บอกคอมพิวเตอร์ถึงขนาดของหน่วยความจำที่ต้องจอง และบอกถึง data type ของข้อมูลที่จะนำไปเก็บ นั่นคือสำรองเนื้อที่ในหน่วยความจำให้มีขนาดใหญ่เพียงพอที่จะเก็บเลขตำแหน่ง และ data type ของข้อมูลที่จะนำไปเก็บ (คือเลขตำแหน่ง)

ท่านอาจสงสัยว่าทำไมเวลาประกาศ pointer จึงต้องใส่ data type การที่จะเข้าใจคำตอบ ท่านจะต้องเข้าใจเรื่อง pointer เป็นอย่างดีเสียก่อน ตัวอย่างต่อไปนี้จะทำให้เข้าใจดีขึ้น

    int productNumber;
    double price;
    double* ptPrice;
    int* ptProductNumber;

โปรแกรมนี้ประกาศตัวแปรสี่ตัว บรรทัดแรกประกาศตัวแปรชนิด int ชื่อ productNumber บรรทัดที่สองประกาศตัวแปรชนิด double ชื่อ price และสองบรรทัดสุดท้ายประกาศตัวแปรแบบ pointer

ข้อมูลชนิด double ที่ใช้ประกาศ ptPrice ทำหน้าที่บอกคอมพิวเตอร์ว่าเลขตำแหน่งที่จะเก็บไว้ใน ptPrice เป็นเลขตำแหน่งของข้อมูลที่มี data type เป็น double และ ptPrice ใช้ชี้ไปยังตัวแปร price

ในทำนองเดียวกัน ข้อมูลชนิด int ที่ใช้ประกาศ ptProductNumber บอกคอมพิวเตอร์ว่าเราจะใช้ ptProductNumber เก็บเลขตำแหน่งของ data type แบบ int และจะใช้มันเก็บเลขตำแหน่งของตัวแปร productNumber

เพราะอะไรถึงต้องบอกคอมพิวเตอร์ว่า เลขตำแหน่งที่จะเก็บเป็น data type อะไร คำตอบคือ เพราะเราต้องการดำเนินการทางคณิตศาสตร์กับ pointer การจะทำเช่นนั้นได้ต้องให้คอมพิวเตอร์รู้ว่า data type ที่เลขตำแหน่งอ้างถึง เป็น data type ใด ท่านจะได้เรียนวิธีกระทำทางคณิตศาสตร์กับ pointer ภายหลัง

 

การกำหนด address ให้ pointer

วิธีนำเลข address ไปใส่ pointer ทำได้โดยใช้เครื่องหมาย & (address operator) ก่อนอื่นมาทบทวนวิธีกำหนดค่าให้ตัวแปรธรรมดากันก่อน คำสั่งข้างล่างบอกให้คอมพิวเตอร์คัดลอกข้อมูลในหน่วยความจำของ foo ไปใส่ในหน่วยความจำของ oldFoo

oldFoo = foo; 

คำสั่งนี้ระบุเป็นนัยว่า เราต้องการทำงานกับข้อมูลที่อยู่ในตัวแปร ไม่ใช่กับเลข address ของมัน แต่ถ้าเราใส่เครื่องหมาย & นำหน้า เท่ากับเราบอกคอมพิวเตอร์ว่า เราต้องการทำงานกับเลข address ของมัน ไม่ใช่ข้อมูลของมัน
ตัวอย่างต่อไปนี้แสดงวิธีใช้เครื่องหมาย & เพื่อบอกคอมพิวเตอร์ให้คัดลอกเลข address ของตัวแปรไปใส่ pointer โดยเราจะสั่งให้นำเลขตำแหน่งของตัวแปร foo ไปใส่ใน pointer ptFoo และบอกให้นำเลข address ของตัวแปร productNumber ไปใส่ไว้ใน pointer ptProductNumber

    ptFoo = &foo;
    ptProductNumber = &productNumber; 

 

การเข้าถึงข้อมูลที่ pointer ชี้อยู่

ปรกติเราใช้ pointer ชี้หน่วยความจำ แต่บางครั้งเราก็ต้องการคัดลอก address จาก pointer ตัวหนึ่ง ไปใส่ pointer อีกตัวหนึ่ง ซึ่งทำได้โดยใช้เครื่องหมาย = ดังนี้

ptOldfoo = ptNewfoo; 

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

บางครั้งนักเขียนโปรแกรมอาจต้องการเข้าถึงข้อมูล ณ address ที่ pointer ชี้อยู่ เรื่องนี้อาจเข้าใจยากสักหน่อย ดังนั้นจะอธิบายโดยยกตัวอย่างให้ชัดเจน โปรดพิจารณาตัวอย่างโค้ดต่อไปนี้

char oldFoo;
char foo = "A";
char *ptFoo;
char *ptOldFoo;
ptFoo = &foo; 

บรรทัดแรกประกาศตัวแปร บรรทัดที่สองประกาศตัวแปร และกำหนดค่าไปพร้อมๆ กันในบรรทัดเดียว บรรทัดที่สามและสี่ประกาศตัวแปรแบบ pointer และบรรทัดสุดท้าย เป็นการคัดลอก address ของตัวแปร foo ไปเก็บไว้ใน pointer ชื่อ ptFoo

สมมุติว่านักเขียนโค้ดต้องการอ่านค่าจากตัวแปร grade เพื่อนำมาแสดงบนหน้าจอ แต่ต้องการทำด้วยการใช้ pointer เท่านั้นก็ทำได้โดยใช้เครื่องหมายดอกจัน (*) นำหน้า pointer เพื่อบอกคอมพิวเตอร์ว่าจะอ่านข้อมูลจากหน่วยความจำที่ pointer ตัวนั้นเก็บ address หากไม่ใส่เครื่องหมายดอกจัน จะเป็นการบอกคอมพิวเตอร์ว่าต้องการอ่านเลข address ที่ pointer ตัวนั้นเก็บอยู่

สมมุติว่าท่านต้องการคัดลอกเลขตำแหน่งที่เก็บอยู่ใน pointer ชื่อ ptFoo ไปใส่ไว้ใน pointer ชื่อ ptOldFoo จะทำได้ดังนี้

ptOldFoo = ptFoo;

ต่อไปสมมุติว่าท่านต้องการคัดลอกข้อมูลจากตัวแปร foo ไปใส่ยังตัวแปรตัวใหม่ชื่อ oldFoo แต่ท่านต้องการทำโดยใช้ pointer ชื่อ ptFoo และใช้เครื่องหมาย *เท่านั้นก็สามารถทำได้ดังนี้

char oldFoo = *ptFoo;

คำสั่งนี้บอกคอมพิวเตอร์ให้นำข้อมูลจากหน่วยความจำตำแหน่งที่ pointer ชื่อ ptFoo เก็บ address ไว้ มาใส่ในตัวแปรชื่อ oldFoo จงใช้ pointer ร่วมกับเครื่องหมาย *เมื่อใดก็ตามที่ท่านต้องการข้อมูล ณ address ที่ pointer ชี้อยู่ และใช้ pointer ร่วมกับเครื่องหมาย *ได้ทุกแห่งที่ใช้ตัวแปรธรรมดาได้

 

 

Pointer to pointer

ภาษา C# นอกจากจะมี pointer ที่ชี้ไปยังข้อมูลที่มี data type ทั่วไปแล้ว ภาษา C# ยังมี pointer แบบพิเศษ ซึ่งเป็น pointer ที่ชี้ไปยัง pointer (pointer to pointer หรือ Pointer Reference ต่อไปจะเรียกว่า PTP) เช่นเดียวกับภาษา C++

วิธีประกาศ PTP เป็นดังนี้

int foo = 12;              // foo คือตัวแปรธรรมดา
int* ptBar = &foo;         // ptBar คือ pointer ธรรมดา
int** ptptFooBar = &ptBar; // ptptFooBar คือ PTP

ไม่เหมือนกับตัวแปรแบบ pointer ธรรมดาที่เราใช้เก็บเลขตำแหน่งหน่วยความจำ (memory address) ของตัวแปรธรรมดา เพราะสิ่งที่ PTP เก็บคือ memory address ของตัวแปรแบบ pointer

 

 

pointer กับ array

ในภาษา C# pointer และ array มันถูกนำมาอธิบายไว้ด้วยกัน เพราะ pointer กับ array มีหลายอย่างที่คล้ายๆ กัน และอันที่จริงเราสามารถสร้าง array จาก pointer ได้ ซึ่งแม้จะมีข้อเสียที่ใช้งานได้ยากกว่า แต่ก็มีข้อดีที่ทำงานได้เร็วกว่า
Pointer และ array มักถูกนำมาใช้ร่วมกันอยู่เสมอเนื่องจาก array เป็นข้อมูลในหน่วยความจำที่เรียงติดกันไป เราจึงอาจใช้ pointer เพื่อวนเข้าถึงข้อมูลเหล่านั้น โปรดพิจารณาโค้ดต่อไปนี้

    int[] myArray = { 1, 2, 3, 4 };
    unsafe
    {
        fixed (int* ptArray = myArray)
        {
            for (int i = 0; i < myArray.Length; i++)
            {
                Console.Write(ptArray[i]);
            }
        }
    } 

โค้ดที่เห็นข้างบนนี้สาทิตการใช้ pointer เข้าถึงข้อมูลใน array (array element) โดยตอนแรกเราสร้าง array ชื่อ myArray จากนั้นสร้าง pointer ที่ชี้ไปยัง array ชื่อ ptArray คำสั่ง fixed ทำหน้าที่ตั้งให้ pointer ชี้ไปยังตัวแปรแบบ managed และตรึงค่าของตัวแปรไว้ไม่ให้เปลี่ยนตำแหน่งระหว่างโปรแกรมทำงาน

ผลลัพธ์เมื่อโปรแกรมทำงานคือ
1234

 

array of pointer

เนื่องจาก pointer ก็คือตัวแปรชนิดหนึ่ง ดังนั้นเราจึงสามารถเก็บ pointer ไว้ใน array ได้เช่นกัน วิธีสร้าง array of pointer (ต่อไปจะเรียกย่อว่า AOP) คล้ายคลึงกับการสร้าง array สามัญ ผลลัพธ์ที่ได้คือ array ที่มี array element เป็น pointer เราจะสร้าง AOP ให้มีกี่มิติก็ได้ และจะสร้าง AOP เป็นแบบ rectangular หรือแบบ non-rectangular (jagged array ดูบทที่ 3) ก็ได้เช่นเดียวกับ array สามัญ

วิธีประกาศ AOP เป็นดังนี้

    int a = 1, b = 2, c = 3;
    int*[] ptArray = { &a, &b, &c }; 

เมื่อรันโค้ดสองบรรทัดข้างบนนี้ ผลลัพธ์ที่ได้คือ AOP ชื่อ ptArray ซึ่งมี array element เป็นค่าตำแหน่งในหน่วยความจำ (memory address) ของตัวแปรแบบ int สามตัวคือ a, b และ c ตามลำดับ

 

array of pointer to pointer

โดยใช้หลักการเดียวกับ AOP เราสามารถสร้าง array เพื่อใช้เก็บ PTP ได้เช่นกัน เราเรียก array ชนิดนี้ว่า array of pointer to pointer (ต่อไปจะเรียกย่อย่า APTP) ซึ่งเป็น array ที่มี array element เป็น PTP ทั้งหมด ตัวอย่างวิธีประกาศ APTP เป็นดังนี้

    int* ptA = &a, ptB = &b, ptC = &c;
    int** pt2ptA = &ptA, pt2ptB = &ptB, pt2ptC = &ptC;
    int**[] arrayOfpt2pt = { pt2ptA, pt2ptA, pt2ptA };

แม้การประกาศ APTP จะดูเรียบง่าย คล้ายการประกาศ array ตามปรกติ แต่การนำ APTP ไปประยุกต์ใช้กลับเป็นเรื่องยากที่สุดในศาสตร์คอมพิวเตอร์ เพราะการทำความเข้าใจกับผลลัพธ์ที่ได้จาก array ที่อ้างถึง pointer ที่ชี้ไปยังตำแหน่งข้อมูลของ pointer อีกต่อหนึ่ง (ซึ่ง pointer อีกตัวนั้นเก็บค่าอ้างอิงที่ชี้ไปยังตำแหน่งข้อมูลของของตัวแปรสามัญอีกทอดหนึ่ง) นับเป็นเรื่องซับซ้อนอย่างยิ่ง การเขียนโค้ดเพื่อประยุกต์ใช้ APTP จึงต้องใช้สมาธิมากกว่าปรกติ

APTP มีประโยชน์เมื่อเราต้องเขียนโปรแกรมจัดการกับโครงสร้างข้อมูลขนาดใหญ่ ที่มีข้อมูลจำนวนมาก
การจัดเรียงหรือการประมวลผลต่อข้อมูลโดยใช้ pointer และ array แม้จะทำได้รวดเร็ว (เพราะ CLR จะจองเนื้อที่ในหน่วยความจำไว้เป็นชิ้นเดียวเรียงต่อเนื่องไป) แต่ในกรณีที่มีข้อมูลจำนวนมหาศาล (หลายร้อยล้านรายการ) การจัดเรียงหรือประมวลผลหากกระทำต่อตัวข้อมูลใน array โดยตรงจะยังช้าเกินไป

ดังนั้นแทนที่เราจะใช้ arry เก็บตัวข้อมูลจริง เราจึงต้องเลี่ยงไปใช้ arry เพื่อเก็บ PTP เมื่อต้องการจัดเรียงหรือประมวลผลกับข้อมูล เราจะทำทางอ้อมโดยจัดเรียงหรือประมวลผลกับค่าของ pointer ที่ถูกชี้โดย PTP ที่อยู่ใน APTP แทน ซึ่งทำได้รวดเร็วกว่าการกระทำกับข้อมูลโดยตรงมาก ทำให้การใช้ APTP จัดการกับข้อมูลในโครงสร้างข้อมูลที่มีข้อมูลจำนวนมาก สามารถทำได้ด้วยความเร็วสูง

ข้อสรุปเรื่อง pointer
• ทำหน้าที่ชี้ไปยังตำแหน่งเก็บข้อมูลในหน่วยความจำ
• การประกาศ pointer ต้องกำหนด data type ด้วย
• ใช้ pointer ชี้ไปยัง pointer หรือชี้ไปยัง array หรือ array ของ pointer ก็ได้
• pointer ที่ชี้ไปยัง pointer ใช้ดอกจันสองตัว เช่น int** p
• pointer arithmetic เป็นไปตาม data type
• ใช้งานได้เฉพาะในบล็อก unsafe

 

 

Unsafe Code

ก่อนจะใช้ตัวแปร pointer ในภาษา C# ได้ท่านต้องประกาศโค้ดบล็อกนั้นให้เป็นแบบ unsafe code โดยใช้คำสั่งดังนี้

unsafe
{
     // โค้ดภายใน block นี้เป็น unsafe code
}

แม้คำว่า unsafe code จะแปลว่า “โค้ดที่ไม่ปลอดภัย” แต่ความหมายจริงๆ แล้วไม่ใช่เช่นนั้น โค้ดส่วนที่เป็น unsafe code ไม่ใช่โค้ดที่อันตราย หรือสุ่มเสี่ยงต่อระบบรักษาความปลอดภัยแต่อย่างใด มันเป็นเพียงโค้ดที่ไม่ได้ถูกตรวจสอบการทำงานเท่านั้น เพราะ CLR เพียงแค่ปล่อยให้มันทำงานไปโดยไม่ข้องแวะ หากคุณเขียน unsafe code คุณจะต้องรับหน้าที่ดูแลเรื่องความปลอดภัยและป้องกันความผิดพลาดต่างๆ ที่อาจเกิดขึ้น (เช่น ความผิดพลาดที่อาจเกิดจากการใช้งาน pointer หรือ array bound) ด้วยตนเอง

ข้อสรุปเรื่อง unsafe code
• กำหนดให้กับ block หรือ method หรือ type ได้
• บางสถานการณ์อาจทำให้การทำงานเร็วขึ้น (เพราะไม่มีการตรวจ array bound)
• อาจทำให้เสี่ยงแต่ความเสถียรของระบบ
• ควรใช้เฉพาะเมื่อต้องการใช้ pointer
• ต้องกำหนด option /unsafe ให้คอมไพเลอร์จึงจะคอมไพล์ได้

คำถามประจำบทที่ 4

  1. Statement คืออะไร
  2. สมาชิกของคลาสและ struct คืออะไร
  3. access modifier คืออะไร
  4. type modifier คืออะไร
  5. type modifier volatile ทำหน้าที่อะไร
  6. คำว่า assembly ในภาษา C# คืออะไร
  7. metadata คืออะไร
  8. field คืออะไร
  9. derived class สามารถมองเห็นและใช้งาน field แบบ private ที่นิยามไว้ใน base class ได้หรือไม่
  10. ฟังค์ชันในภาษา C# คืออะไร
  11. property คืออะไร
  12. access modifier ของ property จะเป็นอะไรเสมอ
  13. method คืออะไร
  14. การนิยาม method ชื่อเดียวกัน ภายในคลาสเดียวกันหลายๆ ชุดเรียกว่าอะไร
  15. การนิยาม method ใน derived class ซ้ำกับ method ใน base class ได้หากใช้ร่วมกับ keyword new จะเรียกว่าอะไร
  16. การทำ method overriding คืออะไร
  17. constructor คืออะไร
  18. default constructor คืออะไร
  19. destructor คืออะไร
  20. หากไม่ระบุ modifier ของพารามิเตอร์ CLR จะถือว่าเป็นการส่งค่าแบบใด
  21. ref parameter คืออะไร
  22. parameter array คืออะไร
  23. method signature คืออะไร
  24. การคอมไพล์โปรแกรมคืออะไร

เฉลยคำถาม

ตอนต่อไป : Inheritance และ Versioning ในภาษา C#