Trong bài này chúng ta sẽ tìm hiểu:

  • Khai báo, tạo và sử dụng kiểu liệt kê
  • Khai báo, tạo và sử dụng kiểu cấu trúc
  • So sánh kiểu cấu trúc và kiểu lớp

Kiểu liệt kê (Enumeration)

Giả sử bạn muốn thể hiện các mùa trong năm (spring, summer, winter, fall). Một giải pháp là sử dụng các số nguyên 0, 1, 2, 3 để thể hiện các mùa nhưng cách này không “tự nhiên”. C# cung cấp một giải pháp hiệu quả hơn là sử dụng biến kiểu liệt kê (enumeration).

Khai báo một kiểu liệt kê theo cú pháp:


enum nhãn_kiểu {giá trị 1, giá trị 2,…}

Ví dụ tạo một kiểu liệt kê với nhãn là Season như sau:


enum Season {Spring, Summer, Fall, Winter}

Sau khi khai báo một kiểu liệt kê, chúng ta có thể khai báo biến như ví dụ sau:


enum Season { Spring, Summer, Fall, Winter }

class Example

{

  public void Method(Season parameter)

  {

    Season localVariable;// biến cục bộ kiểu Season

    ...

  }

  private Season currentSeason;// một field kiểu liệt kê

}

Trước khi đọc giá trị của biến kiểu liệt kê, chúng ta phải gán cho nó một giá trị. Các giá trị được định nghĩa theo kiểu liệt kê chỉ được gán đến các biến kiểu liệt kê như ví dụ sau:


Season s = Season.Spring;// gán giá trị đến biến

MessageBox.Show(s.ToString());// đọc giá trị của biến

Chúng ta đã định nghĩa một kiểu liệt kê với nhãn là Season như sau:


enum Season {Spring, Summer, Fall, Winter}

Mặc định, các phần tử Spring, Summer, Fall, Winter có giá trị số tương ứng trong tập Season là 0,1,2,3. Chúng ta có thể hiển thị phần tử theo kiểu chuỗi hay số (bằng cách ép sang kiểu số tương ứng) như ví dụ sau:


Season s = Season.Spring;

MessageBox.Show(s.ToString());// Kết quả: Spring

MessageBox.Show(((int)s).ToString());// Kết quả: 0

Chúng ta cũng có thể định nghĩa lại các giá trị số của các phần tử kiểu liệt kê, ví dụ:


enum Season {Spring = 1, Summer, Fall, Winter}

lúc này các giá trị tương ứng của các phần tử Spring, Summer, Fall, Winter sẽ là 1, 2, 3, 4. Hiển thị lại Spring:


Season s = Season.Spring;

MessageBox.Show(s.ToString());// Kết quả: Spring

MessageBox.Show(((int)s).ToString());// Kết quả: 1

Chúng ta được phép khai báo nhiều phần tử có cùng giá trị. Ví dụ, Fall và Autumn có ý nghĩa như nhau nên chúng ta có thể khai báo:


enum Season {Spring = 1, Summer, Fall, Autumn = Fall, Winter}

Hiển thị biến với giá trị là Autumn:


Season s = Season.Autumn;

MessageBox.Show(s.ToString());// Kết quả: Fall

MessageBox.Show(((int)s).ToString());// Kết quả: 3

Có thể hiển thị các phần tử liên tiếp nhau trong kiểu liệt kê như ví dụ:


Season s = Season.Fall;

MessageBox.Show(s.ToString());// Kết quả: Fall

s++;

MessageBox.Show(s).ToString());// Kết quả: Winter

Kết quả lệnh Show() thứ hai sẽ là Winter vì Autumn và Fall là một nên phần tử kế tiếp của Fall sẽ là Winter. Nếu phần tử kế tiếp không tồn tại, kết quả sẽ là giá trị số, ví dụ:


Season s = Season.Winter;

MessageBox.Show(s.ToString());// Kết quả: Winter

s++;

MessageBox.Show(s).ToString());// Kết quả: 5

Mặc định, khi khai báo kiểu liệt kê, các phần tử (như Spring, Fall,…) sẽ có giá trị là kiểu int. Chúng ta có thể  khai báo các kiểu khác cho các phần tử như ví dụ các phần tử có kiểu short như khai báo sau:


enum Season:short {Spring, Summer, Fall, Winter}

Chúng ta có thể tiết kiệm bộ nhớ  bằng cách chọn kiểu dữ liệu thích hợp cho các phần tử.

Kiểu cấu trúc (Structure)

Một cấu trúc (structure) giống như một lớp (class) cũng bao gồm các fields, methods, và constructors. Điểm khác biệt class là kiểu tham chiếu được quản lý trên bộ nhớ heap còn structure là kiểu giá trị được quản lý trên stack.

Các kiểu dữ liệu sơ cấp như int, long, float, v.v. là các nặc danh (aliases) cho các cấu trúc System.Int32, System.Int64, System.Single, v.v. Bảng sau mô tả chi tiết về các kiểu sơ cấp:

Kiểu alias Structure/Class
System.Byte byte Structure
System.Int16 short Structure
System.Int32 int Structure
System.Int64 long Structure
System.Single float Structure
System.Double double Structure
System.Decimal decimal Structure
System.Boolean bool Structure
System.DateTime n/a Structure
System.Char char Structure
System.String string Class
System.SByte sbyte Structure
System.UInt16 ushort Structure
System.UInt32 uint Structure
System.UInt64 ulong Structure
System.Object object Class

Các cấu trúc chứa các phương thức. Ví dụ tất cả các cấu trúc ở bảng trên đều cung cấp phương thức ToString() để chuyển một giá trị số sang chuỗi hay phương thức Parse trong cấu trúc System.Int32 dùng để chuyển chuỗi số sang số nguyên tương ứng. Đoạn mã sau minh hoạ hai phương thức ToStringParse:


int i = 12;

MessageBox.Show(i.ToString());//12

string s = "15";

i = int.Parse(s);// hay i = Int32.Parse(s);

MessageBox.Show(i.ToString());//15

Cấu trúc cũng chứa các fields (tĩnh hay không tĩnh). Ví dụ cấu trúc System.Int32 chứa field Int32.MaxValue chỉ giá trị lớn nhất của kiểu int hay field Int32.MinValue chỉ giá trị nhỏ nhất của kiểu int.

Khai báo kiểu cấu trúc

Chúng ta có thể khai báo kiểu cấu trúc cho riêng mình bằng cách dùng từ khoá struct được theo sau bởi tên của cấu trúc và được theo bởi phần thân của cấu trúc giữa hai dấu ngoặc {} như sau:


struct struct_name

{

  // fields, constructors, methods

}

Ví dụ khai báo cấu trúc tên Time với các fields là hours, minutes, seconds, phương thức Hours() và construtor:


struct Time

{

  private int hours, minutes, seconds;  // fields

  public Time(int hh, int mm, int ss)  //constructor

  {

   hours = hh % 24;

   minutes = mm % 60;

   seconds = ss % 60;

  }

  public int Hours()  //method

 {

  return hours;

 }

 ...

}

So sánh cấu trúc và lớp

Lớp và cấu trúc có cách khai báo gần giống nhau nhưng giữa chúng vẫn có những khác biệt quan trọng. Bảng dưới đây sẽ chỉ ra những khác biệt đó.

Structure Class
Một structure là một kiểu giá trị Một class là một kiểu tham chiếu
Các thể hiện (instances)structure được gọi là các giá trị (values) và tồn tại trên stack. Các thể hiện (instances) class được gọi là các đối tượng (objects) và tồn tại trên heap.
Không thể khai báo một constructor mặc định (không có tham số). Trình biên dịch sẽ phát sinh tự động constuctor này. Có thể khai báo một constructor mặc định. Trình biên dịch chỉ phát sinh constructor mặc định nếu chúng ta chưa khai báo constructor nào.
Trình biên dịch sẽ vẫn phát sinh constructor mặc định ngay cả khi chúng ta đã khai báo các constructor. Trình biên dịch không phát sinh constructor mặc định nếuchúng ta đã khai báo các constructor.
Các fields phải được khởi tạo trong constructor. Trình biên dịch không khởi tạo tự động. Trình biên dịch sẽ khởi tạo các fields một cách tự động nếu chúng ta chưa khởi tạo chúng trong constructor.
Chúng ta không được phép khởi tạo các fields tại vị trí khai báo. Chúng ta được phép khởi tạo các fields tại vị trí khai báo.

Khai báo các biến kiểu cấu trúc

Sau khi định nghĩa một kiểu cấu trúc, chúng ta có thể dùng nó để khai báo các biến giống các kiểu khác. Ví dụ sau minh hoạ cách khai báo biến kiểu cấu trúc:


struct Person

{

  //fields

  private string Name;

  private int Age;

  //constructor

  public Person(string Name, int Age) {

   this.Name = Name;

   this.Age = Age;

  }

  //methods

  public string getName() {

    return Name;

  }

  public int getAge() {

   return Age;

  }

}

Khai báo hai biên p1 và p2 kiểu Person:


Person p1 = new Person("Minh",23);

Person p2 = new Person("Dung",24);

MessageBox.Show(p1.getName() +" "+ p1.getAge().ToString());

MessageBox.Show(p2.getName() +" "+ p2.getAge().ToString());

Học C# và WPF >