ในตอนนี้จะพยายเขียนให้เข้าใจง่ายที่สุด สำหรับผู้ที่เพิ่งเริ่มให้เข้าใจกระบวนการ instantiate object
คำสั่งในภาษา C# เช่นคำสั่ง new อาจมีการใช้งานได้เกินหนึ่งรูปแบบซึ่งก็คือ
ในการสร้าง object เราสามารถใช้คำสั่งแบบนี้
จะเห็นว่า หลังคำว่า new จะเป็นชื่อของ constructor ของ class ที่เราต้องการสร้าง นั้นก็คือ Beer() ซึ่ง constructor คือ method ของ class ที่จะทำงานเมือเราต้องการจะสร้าง class นั้นๆ เช่น class Beer ข้างล่าง
จะเห็นว่า ใน class Beer มีสอง method ที่ไม่มี return type และมีชื่อเหมือนกันกับชื่อ class คือ "Beer()" และ "Beer(string name)" (constructor overload) นั้นหมายความว่า เราสามารถสร้าง object จาก constructor ตัวใดตัวหนึ่งจากสองตัวนี้ก็ได้ ทำให้การสร้าง object มีความยืดหยุ่น และสามารถประยุคเทคนิคในการสร้างและจัดการ object ได้หลากหลาย
จากตัวอยากข้างบน "new Beer()" code จะทำงานดังนี้
ข้างบนนี้คือ MSIL ที่ได้หลังจาก compile คำสั่ง new Beer(); การทำงานคร่าวๆ ก็คือ
อ่าว แล้วงี้เราจะไปใช้ไอ้ตัว object นี้ได้ไงอ่ะ คำตอบคือ เราก็สร้างตัวแปร เพื่อที่จะอ้างอิงไปหาไอ้ instance ที่อยู่ใน heap นั้นไงล่ะ, ง่ายๆ ด้วย syntax ที่คุ้นเคย แบบนี้
จะเห็นว่า มีส่วนเพิ่มขึ้นมาสามส่วนจากหน้าไปหลังคือ
MSIL ที่ compile ออกมาจะต่างออกไปหน่อย ซึ่งการทำงานคร่าวๆ จะเป็นประมาณนี้
บรรทัดข้างล่างนี้ เป็นการสร้าง instance ของ class Singha แล้ว assign ใส่ตัวแปลที่ชื่อว่า beer ซึ่งมี type เป็น Singha
ซึ่งกระบวนการสร้าง object แบบนี้จะต่างจากแบบก่อนหน้านิดหน่อย เพราะว่า class Singha implement class Beer ทำให้เวลาสร้าง object Singha ใน heap จะมีการสร้างและเรียก constructor ของ class แม่มันด้วย ซึ่งก็คือ Beer ทำให้ใน managed heap มี instance อยู่สองตัว คือ
นอกจาก method hiding ก็ยังมีการทำ method overriding อีกตัว ซึงเป็นการเปลี่ยนการทำงานของ method ใน derived class ต่างจากการทำ hiding ที่จะทับการทำงานเดิม
ผลรัน
คำสั่งในภาษา C# เช่นคำสั่ง new อาจมีการใช้งานได้เกินหนึ่งรูปแบบซึ่งก็คือ
- ใช้ในการทำ method hiding
- ใช้สร้าง object
ในการสร้าง object เราสามารถใช้คำสั่งแบบนี้
new Beer();
จะเห็นว่า หลังคำว่า new จะเป็นชื่อของ constructor ของ class ที่เราต้องการสร้าง นั้นก็คือ Beer() ซึ่ง constructor คือ method ของ class ที่จะทำงานเมือเราต้องการจะสร้าง class นั้นๆ เช่น class Beer ข้างล่าง
class Beer
{
String Name;
public Beer() : this("Noname")
{
}
public Beer(string name)
{
this.Name = name;
}
}
จะเห็นว่า ใน class Beer มีสอง method ที่ไม่มี return type และมีชื่อเหมือนกันกับชื่อ class คือ "Beer()" และ "Beer(string name)" (constructor overload) นั้นหมายความว่า เราสามารถสร้าง object จาก constructor ตัวใดตัวหนึ่งจากสองตัวนี้ก็ได้ ทำให้การสร้าง object มีความยืดหยุ่น และสามารถประยุคเทคนิคในการสร้างและจัดการ object ได้หลากหลาย
จากตัวอยากข้างบน "new Beer()" code จะทำงานดังนี้
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 8 (0x8)
.maxstack 8
IL_0000: nop
IL_0001: newobj instance void SampleDynamicBinding.Beer::.ctor()
IL_0006: pop
IL_0007: ret
} // end of method Program::Main
ข้างบนนี้คือ MSIL ที่ได้หลังจาก compile คำสั่ง new Beer(); การทำงานคร่าวๆ ก็คือ
- เรียก constructor เพื่อสร้าง instance ของ class Beer
- จองและจัดสรรนหน่วยความจำ เพื่อเก็บ instance ของ Beer ในที่นี้คือ managed heap
- จบ
อ่าว แล้วงี้เราจะไปใช้ไอ้ตัว object นี้ได้ไงอ่ะ คำตอบคือ เราก็สร้างตัวแปร เพื่อที่จะอ้างอิงไปหาไอ้ instance ที่อยู่ใน heap นั้นไงล่ะ, ง่ายๆ ด้วย syntax ที่คุ้นเคย แบบนี้
Beer beer = new Beer();
จะเห็นว่า มีส่วนเพิ่มขึ้นมาสามส่วนจากหน้าไปหลังคือ
- Beer คือ ชื่อของ type นั้นก็คือ class Beer ซึ่งต้องมี type ตรงกันกับกับ object ที่เราสร้างด้วยคำสั่ง new
- beer คือชือของตัวแปร
- เครื่องหมาย "=" คือการ assign ค่าอ้างอิงไปยัง instance ของ Beer ที่เราสร้างดังที่อธิบายไปแล้ว
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 8 (0x8)
.maxstack 1
.locals init ([0] class SampleDynamicBinding.Beer beer)
IL_0000: nop
IL_0001: newobj instance void SampleDynamicBinding.Beer::.ctor()
IL_0006: stloc.0
IL_0007: ret
} // end of method Program::Main
- หลังจากที่มีการสร้าง instance ของ class และเก็บไว้ใน managed heap ไปแล้ว
- จองเนื้อที่ใน stack เพื่อเก็บตัวแปร "beer" ซึ่งมี type เป็น Beer
- ทำการกำหนดค่า reference ของ beer ไปยัง instance ในข้อ 1.
ทีนี้ เราก็สามารถใช้ instance ของ class Beer ได้แล้ว โดยการใช้ผ่านตัวแปร beer นั้นเอง ซึ่งการกำหนดค่าอ้างอิงของ instance ไปยังตัวแปรที่มี type ตรงกัน เราเรียกว่า name binding
การ binding แบ่งออกเป็น 2 แบบคือ static binding และ dynamic binding
- Static binding คือ การ กำหนดค่าอ้างอิงไปยังตัวแปลได้เลย ขณะ compile time ดังตัวอย่างข้างต้น
- Dynamic binding คือ การกำหนดค่าอ้างอิงไปยังตัวแปลของ object ขณะ run-time ซึ่งวิธีนี้ ทำให้การออกแบบซอร์ฟแวรได้ยืดหยุ่น และหลากหลาย สนับสนุนหลักการ polymorphism เช่น dependency injection เป็นต้น
ตัวอย่างด้านล่าง จะเป็นการสาธิตการทำ dynamic binding และการทำ method hiding ด้วย new modifier นะครับ จาก class Beer เราจะเพิ่ม class ที่ชือว่า Singha เข้ามา ซึ่งนี้ implement class Beer อยู่ (inherit)
class Beer
{
string Name;
public Beer() : this("Noname")
{
}
public Beer(string name)
{
this.Name = name;
}
public void WriteName()
{
Console.WriteLine(this.Name);
}
}
class Singha : Beer
{
string Name;
public Singha() : this("Singha")
{
}
public Singha(string name)
{
this.Name = name;
}
public new void WriteName()
{
Console.WriteLine(this.Name);
}
}
บรรทัดข้างล่างนี้ เป็นการสร้าง instance ของ class Singha แล้ว assign ใส่ตัวแปลที่ชื่อว่า beer ซึ่งมี type เป็น Singha
Singha beer = new Singha();
ซึ่งกระบวนการสร้าง object แบบนี้จะต่างจากแบบก่อนหน้านิดหน่อย เพราะว่า class Singha implement class Beer ทำให้เวลาสร้าง object Singha ใน heap จะมีการสร้างและเรียก constructor ของ class แม่มันด้วย ซึ่งก็คือ Beer ทำให้ใน managed heap มี instance อยู่สองตัว คือ
- instance ของ class Singha
- instance ของ class Beer
Beer beer = new Singha();
แต่เราไม่สามารถทำแบบนี้ได้
เพราะว่า เราเรียก constructor ของ Beer จะไม่มีการสร้าง instance ของ class Singha, การทำ type conversion จะเป็นจาก base class ไป derived class เท่านั้น ไม่งั้นจะเกิด error แบบนี้
Cannot implicitly convert type 'SampleDynamicBinding.Beer' to 'SampleDynamicBinding.Singha'. An explicit conversion exists (are you missing a cast?)
ตัวอย่าง
ผลรัน
Singha beer = new Beer();
เพราะว่า เราเรียก constructor ของ Beer จะไม่มีการสร้าง instance ของ class Singha, การทำ type conversion จะเป็นจาก base class ไป derived class เท่านั้น ไม่งั้นจะเกิด error แบบนี้
Cannot implicitly convert type 'SampleDynamicBinding.Beer' to 'SampleDynamicBinding.Singha'. An explicit conversion exists (are you missing a cast?)
ตัวอย่าง
Singha beer = new Singha();
beer.WriteName(); // dynamic type คือ Singha
((Beer)beer).WriteName(); // dynamic type คือ Beer
Singha
Noname
นอกจาก method hiding ก็ยังมีการทำ method overriding อีกตัว ซึงเป็นการเปลี่ยนการทำงานของ method ใน derived class ต่างจากการทำ hiding ที่จะทับการทำงานเดิม
ตัวอย่าง
ผลรัน
Beer beer = new Singha();
beer.WriteName(); // dynamic type คือ Singha
((Beer)beer).WriteName(); // dynamic type คือ Beer
Noname
Noname
ผลรันเป็น Noname ทั้งสองบรรทัดเนื่องจาก class Singha มีการทำ method hinding ทำให้ complier มองเป็นคนละ method กัน ถึงแม้จะเรียกใช้ constructor ของ Singha ซึ่งจะมีการ implicit type conversion จาก Beer เป็น Singha แต่ method ที่ถูกเรียกจะเป็น Beer.WriteName()
ตัวอย่างด้านล่างคือการทำ method overriding
class Beer
{
string Name;
public Beer() : this("Noname")
{
}
public Beer(string name)
{
this.Name = name;
}
public virtual void WriteName()
{
Console.WriteLine(this.Name);
}
}
class Singha : Beer
{
string Name;
public Singha() : this("Singha")
{
}
public Singha(string name)
{
this.Name = name;
}
public override void WriteName()
{
Console.WriteLine(this.Name);
}
}
class Program
{
static void Main(string[] args)
{
Beer beer = new Singha();
beer.WriteName();
((Beer)beer).WriteName();
}
}
ผลรัน
Singha
Singha
จะเห็นว่าการทำ method overriding จะเป็นการทับการทำงานของ method เดิม
No comments:
Post a Comment