หลังจากเริ่มต้นเข้าสู่โลกของ C# ด้วยวลียอดฮิตในการเขียนโปรแกรมครั้งแรกแล้วนะครับ (วลีที่ว่าคือ "Hello World!") คราวนี้เราจะมาดูรายละเอียดที่สำคัญอีกอย่างนึง เวลาที่เราต้องการจะศึกษาภาษาใดภาษานึง สิ่งที่ว่านั้นได้แก่ ชนิดของข้อมูลที่ภาษานั้นๆ มีให้ใช้กันครับ
ชนิดของข้อมูล (Type)
ตัวแปรในภาษา C# สามารถจำแนกออกได้เป็น 2 ชนิดคือ value type และ reference type ตัวแปรประเภท value type โดยทั่วไปก็อย่างเช่น char, int หรือ float รวมถึง enum แล้วก็ struct ด้วยนะครับ ส่วนตัวแปรประเภท referenc type ก็อย่างเช่น class, interface, delegate รวมถึง array
ความแตกต่างคือตัวแปรประเภท value จะมีการเก็บค่าของตัวแปรไว้ที่ตัวมันเลยครับ ส่วน reference มันจะเก็บค่าที่อยู่ของ object ที่มันอ้างถึง ด้วยเหตุนี้เองตัวแปรประเภท reference หลายตัว สามารถอ้างถึง object อันเดียวกันได้ ถ้าเกิดการแก้ไขหรือเปลี่ยนแปลงที่ reference ตัวใต ก็จะส่งผลถึง reference ตัวอื่นที่อ้างถึง object เดียวกัน ถ้าเป็นประเภท value การแก้ไขค่าของข้อมูลจะมีผลต่อตัวแปรตัวนั้นๆ เพียงตัวเดียวเท่านั้น ลองดูตัวอย่าง code ด้านล่างเพื่อความเข้าใจที่ดีขึ้นครับ
[src]
type1.cs
using System;
class Class1
{
public int value = 0;
}
class Test
{
static void Main()
{
int val1 = 0;
int val2 = val1;
val2 = 123;
Class1 ref1 = new Class1();
Class1 ref2 = ref1;
ref2.value = 123;
Console.WriteLine("Values: {0}, {1}",val1,val2);
Console.WriteLine("Ref: {0}, {1}",ref1.value,ref2.value);
}
}
[/src]
ถ้าคุณลอง compile source code ด้านบน และสั่งให้มันทำงานจะได้ผลดังนี้ครับ
Values: 0, 123
Ref: 123, 123
สังเกตได้ว่า การกำหนดค่าให้แก่ val2 ไม่มีผลต่อ val1 ( ถึงแม้เราจะประกาศให้ val2 = val1 ในบรรทัดก่อนหน้านั้นก็ตาม) เพราะทั้ง val1 และ val2 จะมีช่องเก็บข้อมูลของตัวเองไม่เกี่ยวกัน ส่วนการที่เรากำหนดค่าให้แก่ ref2.value = 123 กลับมีผลต่อ ref1 เพราะการกำหนดให้ ref2 = ref1 คือให้ ref2 และ ref1 อ้างไปถึง object ตำแหน่งเดียวกัน การเปลี่ยค่าของ ref2.value จึงมีผลต่อ ref1.value ด้วย
อีกอย่าง ลองสังเกตบรรทัดที่สั่งให้แสดงผลทางหน้าจอนะครับ
Console.WriteLine("Values: {0}, {1}",val1,val2);
Console.WriteLine("Ref: {0}, {1}",ref1.value,ref2.value);
จะเห็นการใช้ {0} และ {1} ถ้าคุณเคยเขียนภาษาซีหล่ะก็ มันจะคล้ายๆ กับการที่ เรา printf แล้วใส่ %d หรือ %c อะไรประมาณนั้นแหละครับ แต่สำหรับ C# แล้ว คุณใช้ {0} หรือ {1} ได้เลยไม่ต้องระบุประเภทตัวแปรเพราะมันจะรู้จากชนิดของตัวแปรที่เราจะให้แสดงผลเองครับ อีกอย่างคือ {n} โดย n จะแทนตำแหน่งของตัวแปรที่เรียงกันถัดจากเครื่องหมาย ".." นั่เองครับ
ประเภทของตัวแปรที่ C# เตรียมไว้ให้แล้ว
C# ดูค่อนข้างจะเอาใจผู้ใช้ C แล้วก็ C++ เป็นพิเศษครับ (มันก็น่าจะเป็นอย่างนั้นแหละนะ เพราะนอกเหนือจากนี้ก็เป็นโปรแกรมเมอร์ VB อยู่แล้ว ไม่จำเป็นต้องหลอกล่อ เหอ เหอ) โดยตัวแปรที่เตรียมไว้ให้ก็จะมีชื่อเหมือนๆ กับที่เรา assign ใน ภาษา C หรือ C++ นั่นแหละครับ อย่างเช่น
ถ้าเป็นประเภท reference type ก็จะเป็น object และ string ตัวแปรประเภท object เป็นตัวแปรพื้นฐานของบรรดาตัวแปรทั้งหมดครับ (แน่นอน เพราะตัวแปรอื่นๆ ก็เกิดมาจาก object เนี้ยแหละ) ส่วน string จะใช้เก็บข้อมูลประเภทข้อความ
ส่วนในหมวดของ value type ก็จะได้แก่ int, char, float และอื่นๆ อีกมากมายครับ ดูจากตารางด้านล่าง
ชนิด รายละเอียด ตัวอย่างการใช้งสาน
object The ultimate base type of all other types object o = null;
string String type; a string is a sequence of Unicode characters type name. The example string s = "hello";
sbyte 8-bit signed integral type sbyte val = 12;
short 16-bit signed integral type short val = 12;
int 32-bit signed integral type int val = 12;
long 64-bit signed integral type long val1 = 12;
long val2 = 34L;
byte 8-bit unsigned integral type byte val1 = 12;
ushort 16-bit unsigned integral type ushort val1 = 12;
uint 32-bit unsigned integral type uint val1 = 12;
uint val2 = 34U;
ulong 64-bit unsigned integral type ulong val1 = 12;
ulong val2 = 34U;
ulong val3 = 56L;
ulong val4 = 78UL;
float Single-precision floating point type float val = 1.23F;
double Double-precision floating point type double val1 = 1.23;
double val2 = 4.56D;
bool Boolean type; a bool value is either true or false bool val1 = true;
bool val2 = false;
char Character type; a char value is a Unicode character char val = 'h';
decimal Precise decimal type with 28 significant digits decimal val = 1.23M;
ข้อสังเกตเพิ่มเติม
- ข้อมูลประเภท char และ string จะเป็นมาตรฐานของอักขระ Unicode เนื่องจากปัญหาเรื่องความหลากหลายของตัวอักษรของประเทศบางประเทศครับ Unicode ออกมาช่วยแก้ปัญหานี้ โดยจะขยายจาก char สมัยก่อนที่มีขนาดต่อ 1 อักขระ 8 bits เพิ่มเป็น 16 bits
- decimal ออกมาเพื่อแก้ความผิดพลาดที่เกิดจากการปัดเศษของตังแปรประเภท float ครับ เหมาะที่จะใช้ในการคำนวนเกี่ยวกับงานด้านเงินตราหรือตัวเลขภาษี อะไรประมาณนี้แหละครับ
- ชื่อประเภทที่แสดงในตารางหน่ะ เป็นชื่อเล่นของมันนะครับ โดย C# ตั้งชื่อเล่นขึ้นมาเพื่อให้นักพัฒนาที่คุ้นเคยกับ C หรือ C++ ไม่ต้องเปลี่ยนอะไรมากกว่าที่ควรจะเป็น อย่างเช่น int จริงแล้วก็คือ System.Int32 เห็นมั๊ยครับ แท้จริงแล้วมันก็เป็น Object ดีๆ นี่เอง
การแลกเปลี่ยนระหว่างข้อมูลต่างชนิดกัน
ตัวแปรต่างๆ มีคุณสมบัติในการเปลี่ยนไปเป็นตัวแปรชนิดอื่น ไม่เหมือนกันครับ เช่น int กับ long มันจะไม่มีปัญหาถ้าเราเปลี่ยนจาก int เป็น long แต่ถ้ากลับกัน ถ้าเราเปลี่ยนจาก long มาเป็น int หล่ะ เมื่อ long มันมีขนาดใหญ่กว่า int? การเปลี่ยนประเภทข้อมูลของ C# แบ่งออกได้เป็น 2 รูปแบบครับ คือ implicit conversions และ explicit conversions โดยที่ implicit conversions จะเป็นการเปลี่ยนข้อมูลที่เป็นไปได้ในทันทีครับ อย่างเช่นที่ผมกล่าวถึงการเปลี่ยนจาก int เป็น float นั้นแหละ แต่ถ้าเราต้องการจะเปลี่ยข้อมูลจาก long เป็น int ทำได้โดยการใช้ลักษนะของ explicit conversions ดู 2 ตัวอย่างด้านล่างประกอบครับ
[src]
class Test
{
static void Main(){
int intValue = 123;
long longValue = intValue;
Console.WriteLine("{0}, {1}", intValue, longValue);
}
}
[/src]
ด้านบนแสดงการเปลี่ยนข้อมูลในลักษณะของ implicit ซึ่งทำได้เลย
[src]
class Test
{
static void Main(){
long longValue = int64.MaxValue
Console.WriteLine("(0) {0}, {1}", longValue, intValue);
}
}
[/src]
ส่วนอันนี้เป็นการ convert ในลักษณะของ explicit conversion ครับ ผลที่ได้คือ
(int) 9223372036854775807 = -1
เกิด overflow ครับ เนื่องจากค่า MaxValue ของ long มันมากกว่า ค่า ที่ int จะเก็บได้
ข้อมูลประเภท Array
array อาจจะเป็นได้ทั้ง array มิติเดียว หรือ หลายมิติก็ได้ โดยที่ C# จะรองรับ array ทั้งประเภท "ratangular" และ "jagged" ทั้งสองอย่างต่างกันอย่างไรลองติดตามดูครับ
ก่อนอื่นมาดู array ประเภทธรรมดาๆ ก่อนแล้วกันนะครับ ได้แก่ array มิติเดียว
[src]
array1.cs
using System;
class Test
{
static void Main()
{
int i;
int[] arr = new int[5];
for(i=0; i < arr.Length; i++)
arr[i] = i * i;
for(i=0; i < arr.Length; i++)
Console.WriteLine("arr[{0}] = {1}", i, arr[i]);
}
}
[/src]
ถ้าคุณทดลอง run โปรแกรมที่ compile จาก source code ข้างบนก็จะได้ผลดังนี้ครับ
arr[0] = 0
arr[1] = 1
arr[2] = 4
arr[3] = 9
arr[4] = 16
นี่เป็นเพียง array มิติเดียวที่น่าจะคุ้นเคยกันดีอยู่แล้ว ทีนี้เราลองมาดูครับ ว่าจะสามารถประกาศ array แบบอื่นๆ ได้ยังบ้าง
[src]
class Test
{
static void Main() {
int[] a1; // single-dimensional array of int
int[,] a2; // 2-dimensional array of int
int[,,] a3; // 3-dimensional array of int
int[][] j2; // "jagged" array: array of (array of int)
int[][][] j3; // array of (array of (array of int))
}
}
[/src]
จากตัวอย่างข้างบนจะเป็นการประกาศ array แบบต่างๆ มีรายละเอียกอธิบาย อยู่ในแต่ละบรรทัดแล้วครับ ลองดูการกำหนดค่าให้มันกัน ครับว่า แต่ละประเภทที่ประกาศขึ้นมามันเก็บค่ากันยังไง
class Test
{
static void Main() {
int[] a1 = new int[] {1, 2, 3};
int[,] a2 = new int[,] {{1, 2, 3}, {4, 5, 6}};
int[,,] a3 = new int[10, 20, 30];
int[][] j2 = new int[3][];
j2[0] = new int[] {1, 2, 3};
j2[1] = new int[] {1, 2, 3, 4, 5, 6};
j2[2] = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
}
}
จากตัวอย่างด้านบนแสดงให้เห็นดังนี้ครับ a1,a1 และ a3 เป็น array ในลักษณะที่เรียกว่า rectangular หมายถึงเป็น block สี่เหลี่ยมที่มีขนาดกว้างยาวแน่นอนครับ อย่างเช่น a3 เป็น array 2 มิติ มีขนาด 2x3 หรือมีสมาชิก 6 ตัว ส่วน a3 เป็น array 3 มิติมีการจองช่องสำหรับเก็บข้อมูล ขนาด 10x20x30 ซึ่งก็จะได้สมาชิกที่สามารถอยู่ใน array ได้ทั้งสิ้น 6000 ตัว
ลองพิจารณาที่ j2 ครับ ลักษณะนี้แหละเรียกว่า array แบบ jagged สังเกตว่าเป็น array 2 มิติที่มีจำนวนสมาชิกในมิติที่ 2 ไม่เท่ากัน โดย j2[0] จะมีสมาชิก 3 ตัว ส่วน j2[1] และ j2[2] จะมีสมาชิก 6 และ 9 ตัวตามลำดับ
คราวเราลองมาดูการประกาศประเภทข้อมูลของ สมาชิกใน array และ ขนาดของมัน จากตัวอย่างที่ผ่านมาสังเกตได้ว่า มีการพยายามที่จะประกาศชนิดของ array ก่อน อย่างเช่น int[,,] a3 = new int[10,20,30] โดยระบุชนิดข้อมูลเป็น int และขนาดชอง array เป็น [10,20,30]
แต่จริงๆ แล้วไม่ต้องทำอย่างนั้นเสมอไปก็ได้ครับ เราอาจจะประกาศเป็น int[] a1 = new int[] {1,2,3}; เลยก็ได้ หรือแม้แต่เขียนให้สั้นเข้าไปอีก เป็น int[] a1 = {1,2,3} เลยก็ได้ครับ แต่ถ้าอยู่ดีๆ ประกาศ a1 = {1,2,3} อย่างนี้ไม่ได้นะครับ เป็นต้องมีการระบุชนิดของข้อมูลด้วยเช่น short, int หรือ แม้แต่ long
Type system unification
อันนี้ไม่รู้จะเรียกว่าอะไรดีครับ คือ C# หน่ะ เค้าได้ใส่ไอ้ลักษณะที่เรียกว่า "unified type system" ลงไปในคุณสมบัติอย่างนึงของภาษาด้วยครับ เนื่องจากประเภทของข้อมูลไม่ว่าจะเป็น value type หรือ reference type ก็เถอะ ต่างก็มีรากฐานมาจาก object ทั้งสิ้น ดังนั้นเมื่อมันเป็น object เราก็ต้องเรียก method ของมันได้ เช่นเดียวกับ object ทั่วๆ ไปสิ แน่นอนครับ คุณสามารถที่จะกระทำการกับข้อมูลใดๆ เช่นเดียวกับที่คุณทำกับ object ได้ครับ ลองดูตัวอย่าง
[src]
Class Test
{
static void Main() {
Console.WriteLine(3.ToString());
}
}
[/src]
นั่นไงหล่ะครับ 3 ที่เห็น มันกระทำตัวเป็น object เข้าให้แล้ว โดยคุณสามารถเรียก method ToSting มาใช้ได้เช่นเดียวกับ int ทั่วๆไป
[src]
Class Test
{
static void Main() {
int i = 123;
object o = i; // boxing
int j = (int) o; // unboxing
}
}
[/src]
ลองดูตัวอย่างนี้ครับ ตัวอย่างพยายามแสดงให้เห็นว่า int หน่ะ สามารถเปลี่ยนไปเป็น object และยังวามารถเปลี่ยนกลับมาเป็น int ได้อีกที เราเรียกการทำงานลักษณะนี้ของ C# ว่า boxing และ unboxing เมื่อข้อมูลชนิด value type ต้องการเปลี่ยนตัวเองไปเป็น reference type ระบบจะสร้าง object box ขึ้นมาเพื่อให้ได้เจ้า value type เข้าไปอยู่ ขั้นตอนนี้เรียก boxing ครับ และเมื่อเราต้องการให้มันกลับสู่สภาวะเดิมชองมัน คือกลับมาเป็น value type ระบบก็จะย้ายมันออกมาจาก box สู่ที่ที่เหมาะสมต่อไป ขั้นนี้เรียกว่า unboxing ครับ
เจ้าระบบที่เรียกว่า "type system unification" นี่แหละครับ เตรียมไว้สำหรับกรณีที่เราอยากใช้ตัวแปรในลักษณะ object แต่เราไม่ได้ประกาศมันไว้เป็น object ตั้งแต่เรก ในส่วนที่เราไม่ต้องการที่จะใช้มันในลักษณะชอง object เราก็ใช้มันเป็น int ธรรมดาๆ แต่ถ้าเมื่อไหร่อยากให้มันเป็น object ขึ้นมา ก็สามารถเรียกใช้มันได้ทันทีครับ