C# Cơ bản .NET Core §1) Cài đặt, chương trình C# đầu tiên §2) Biến, kiểu dữ liệu và nhập/xuất §3) Toán tử số học và gán §4) So sánh, logic và lệnh if, switch §5) Vòng lặp for, while §6) Phương thức - Method §7) Phương thức - Delegate §8) Lớp - Class §9) Kiểu vô danh và dynamic §10) Biểu thức lambda §11) Event §12) Hàm hủy - Quá tải toán tử - thành viên tĩnh - indexer §13) Lớp lồng nhau - namespace §14) null và nullable §15) Mảng §16) Chuỗi ký tự §17) Tính kế thừa §18) Tính đa hình - abstract - interface §19) Struct và Enum §20) Ngoại lệ Exeption §21) IDisposable - using §22) File cơ bản §23) FileStream §24) Generic §25) Collection - List §26) SortedList §27) Queue / Stack §28) Linkedlist §29) Dictionary - HashSet §30) Phương thức mở rộng §31) ObservableCollection §32) LINQ §33) (Multithreading) async - bất đồng bộ §34) Type §35) Attribute Annotation §36) DI Dependency Injection §37) (Multithreading) Parallel §38) (Networking) HttpClient §39) (Networking) HttpMessageHandler §40) (Networking) HttpListener §41) (Networking) Tcp TcpListenerr/TcpClient §42) (ADO.NET) SqlConnection §43) (ADO.NET) SqlCommand §44) (EF Core) Tổng quan §45) (EF Core) Tạo Model §46) (EF Core) Fluent API §47) (EF Core) Query §48) (EF Core) Scaffold §49) (EF Core) Migration §50) (ASP.NET CORE) Hello World! §51) (ASP.NET CORE) Middleware §52) (ASP.NET CORE) Map - Request - Response §53) (ASP.NET CORE) IServiceCollection - MapWhen §54) (ASP.NET CORE) Session - ISession §55) (ASP.NET CORE) Configuration §56) (ASP.NET CORE MVC) Controller - View

Phần này bắt đầu nghiên cứu, triển khai các vấn đề kỹ thuật liên quan đến Lập trình hướng đối tượng.

Lập trình hướng đối tượng (Object-oriented programming - OOP), là kỹ thuật lập trình mà điều cốt yếu cần trừu tượng hóa các vấn đề thành các đối tượng (đối tượng có dữ liệu và các ứng xử). Kỹ thuật OOP có 4 tính chất

  • Tính trừu tượng (abstraction) mô ta một cách tổng quát hóa mà không chi tiết thông tin (triển khai với interface, abstract)
  • Tính đóng gói (encapsulation) dữ liệu đối tượng cố gắng như nằm trong một hộp đen, các thành phần khác bên ngoài đối tượng không trực tiếp tác động đến dữ liệu
  • Tính đa hình (polymorphism) đối tượng ứng xử khác nhau tùy trường hợp cụ thể
  • Tính kế thừa (inheritance) đặc tính của lớp được kế thừa từ một lớp khác

Khái niệm chung về lớp trong C#

Trong lập trình hướng đối tượng, lớp (class) là một kiểu dữ liệu nó định nghĩa một tập hợp các biến (thuộc tính) và phương thức (gọi chúng là các member - thành viên lớp). Từ lớp đó sinh ra các đối tượng (object), các đối tượng này còn gọi là instance of a class (bản triển khai của class), mỗi đối tượng có giá trị dữ liệu cụ thể (lưu trong thành viên biến). Các phương thức (method) - định nghĩa các ứng xử của đối tượng - dựa theo dữ liệu của chúng.

Ví dụ: Một lớp mô tả các loại vũ khí, đặt tên là Vukhi, thì bên trong nó có thể định nghĩa các thành viên dữ liệu (biến) như: tên vũ khí, độ sát thương, tầm ảnh hưởng ... Cũng có thể định nghĩa các thành viên hàm (phương thức) để mô tả ứng xử của nó như: Ten(), TanCong() ...

Khi có định nghĩa lớp VuKhi, bạn có thể tạo ra đối tượng cụ thể của nó mà mỗi đối tượng có những dữ liệu khác nhau như sung_luc, sung_may ...

Khai báo lớp, sử dụng lớp

Cú pháp, ví dụ khai báo lớp

Khi giải quyết các vấn đề thực tiễn, các vấn đề cần giải quyết bạn cần tìm cách trừu tượng hóa nó thành vấn đề tổng quát, như nó có các đặc tính gì, ứng xử của nó ra sao ... Từ đó mới có thể định nghĩa ra lớp để hiện thực hóa vấn đề trừu tượng được.

Cú pháp cơ bản như sau:

<Access Modifiers> class Class_Name {
    // khai báo các thành viên dữ  liệu (thuộc tính - biến)
     // khai báo các thành viên  hàm (phương thức)
}

Trong đó Access Modifiers có thể là public protected internal private nếu không có thì mặc định là interal

Khai báo và khởi tạo thành viên thuộc tính (biến) tương tự cách khai báo biến thông thường nhưng có sử dụng thêm Access Modifiers để quy định cấp độ truy cập.


Trở lại ví dụ trên, định nghĩa lớp VuKhi

// File VuKhi.cs
using System;

namespace CS07_Class
{
    public class VuKhi
    {
        /// Tên của vũ khí: Súng Lục, Súng Trường, Dao ..
        public string name = "Tên Vũ Khí";

        /// Độ sát thương 10 cấp độ
        int dosatthuong = 0;

        /// Hàm này thiết lập  độ sát thương
        public void SetDoSatThuong(int mucdo)
        {
            dosatthuong = mucdo;
        }

        // In ra: Tên vu khí: * * * * * * * * (bằng độ sát thương)
        public void TanCong()
        {
            Console.Write(name + ": ");
            for (int i = 0; i < dosatthuong; i+ +)
            {
                Console.Write(" * ");
            }
            Console.WriteLine();
        }

    }
}

Lớp VuKhi trên mô tả chung về loại vũ khí, nó có tên lưu ở trường dữ liệu name, có độ sát thương lưu ở trường (biến) dosatthuong, nó có các phương thức SetDoSatThuong để thiết lập độ sát thước, TanCong() trừu tượng hóa khi vú khí đó tấn công.

Tạo và sử dụng đối tượng

Giờ ta tiến hành tạo ra các đối tượng cụ thể, áp dụng VuKhi. Như trên đã nói, class là một kiểu dữ liệu, nên để sử dụng nó có thể khai báo biến với kiểu dữ liệu là tên lớp do bạn định nghĩa. Để tạo ra đối tượng lớp thì dùng từ khóa new với cú pháp new ClassName();

// Khai báo và khởi tạo đối tượng luôn
var ob1 = new ClassName();

// Khai báo, sau đó khởi tạo
ClassName ob2;
ob2 = new ClassName();

Toán tử .

Sau khi đối tượng lớp (object) được tạo, bạn có thể truy cập đến các thuộc tính, trường dữ liệu và phương thức của đối tượng đó bằng ký hiệu . theo quy tắc object.tên_thuộc_tính hay object.tên_phương_thức

Áp dụng:

static void Main(string[] args)
{
    var sungluc = new VuKhi();              // Khai báo và khởi tạo
    sungluc.name = "SÚNG LỤC";              // Truy cập và gán thuộc tính
    sungluc.SetDoSatThuong(10);             // Truy cập (gọi) phương thức

    VuKhi sungtruong = new VuKhi();
    sungtruong.name = "SÚNG TRƯỜNG";
    sungtruong.SetDoSatThuong(50);

    sungluc.TanCong();                      // Gọi phương thức
    sungtruong.TanCong();                   // Gọi phương thức

//Kết quả chạy
//SÚNG LỤC:        *  *  *  *  *  *  *  *  *  *
//SÚNG TRƯỜNG:     *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
                
}

Từ khóa this

Từ khóa this dùng trong các phương thức của lớp, nó tham chiếu đến đối tượng hiện tại sinh ra từ lớp. Sử dụng this để tường minh, tránh sự không rõ ràng khi truy cập thuộc tính, phương thức hoặc để lấy đối tượng lớp làm tham số cho các thành phần khác ...

Ví dụ, hàm SetDoSatThuong, bạn có thể viết:

public void SetDoSatThuong(int dosatthuong)
{
    this.dosatthuong = dosatthuong;
}

Nếu viết thiếu this thì là dosatthuong = dosatthuong, làm cho khó hiểu không biết là gán dosatthuong từ tham số hàm cho dosatthuong là dữ liệu của lớp. Các trường hợp khác, không gây ra sự bối rối kiểu này thì có thể bỏ this

Phương thức khởi tạo - Constructor

Phương thức khởi tạo là phương thức của lớp, nó được thi hành ngay khi đối tượng được tạo (bởi toán tử new), phương thức khởi tạo có tên trùng với tên của lớp, không có kiểu trả về.

Ví dụ bổ sung thêm phương thức khởi tạo vào lớp VuKhi ở trên:

public class VuKhi
{
    /...
    public VuKhi(string name, int dosatthuong)
    {
        this.name  = name;
        SetDoSatThuong(dosatthuong);
    }
    //...
}
    

Lúc này có thể sử như sau:

var sungluc = new VuKhi("SÚNG LỤC", 10);
VuKhi sungtruong = new VuKhi(name: "SÚNG TRƯỜNG", dosatthuong: 50);

sungluc.TanCong();
sungtruong.TanCong();

Kết quả tương tự như ví dụ trên, việc sử dụng hàm khởi tạo đảm bảo dữ liệu của đối tượng bắt buộc phải khởi tạo ngay khi đối tượng đó được tạo - tránh việc sử dụng đối tượng mà dữ liệu không chính xác.

Quá tải (Overloading) phương thức

Kỹ thuật quá tải phương thức (Method Overloading) là cách thức triển khai khái niệm tính đa hình của lập trình hướng đối tượng. Quá tải phương thức là các phương thức có cùng tên nhưng tham số khác nhau (hàm có thể trả về kiểu dữ liệu khác nhau)

Tính đa hình (polymorphism) là cách ứng xử của đối tượng - ứng xử này là khác nhau tùy thuộc vào tình huống cụ thể.

Ví dụ lớp Console của NET CORE (mã nguồn Console.cs) nó quá chồng một loạt phương thức ví dụ:

public static void WriteLine();
public static void WriteLine(bool value);
public static void WriteLine(decimal value);
public static void WriteLine(int value);
...

Điều này giúp cho bạn khi bạn gọi Console.Writeline(a), tùy thuộc vào kiểu dữ liệu của a mà một hàm WriteLine tương ứng được thi hành.

Ví dụ:

public class OverloadingExample {

    public static int Sum(int a, int b)
    {
        return a + b;
    }

    public static double Sum(double a, double b)
    {
        return a + b;
    }
}

Lớp trên có hàm Sum quá tải, tùy thuộc vào kiểu tham số mà hàm Sum cụ thể được gọi.

double a = 1;
double b = 2;
var c  = OverloadingExample.Sum(a, b); // c = 3 có kiểu double
int a = 1;
int b = 2;
var c  = OverloadingExample.Sum(a, b); // c = 3 nhưng có kiểu int

Chú ý: Khai báo hai hàm cùng tên, giống nhau hoàn toàn về tham số chỉ khác kiểu trả về sẽ gây lỗi.

Tìm hiểu tính đóng gói lập trình hướng đối tượng

Tính đóng gói mục đích hạn chế tối đa việc can thiệp trực tiếp vào dữ liệu, hoặc thi hành các tác vụ nội bổ của đối tượng. Nói cách khác, một đối tượng là hộp đen đối với các thành phần bên ngoài, nó chỉ cho phép bên ngoài tương tác với nó ở một số phương thức, thuộc tính, trường dữ liệu nhất định - hạn chế.

C# triển khai tính đóng gói này chính là sử dụng các Access Modifiers: public private protected internal khi khai báo lớp, phương thức, thuộc tính, trường dữ liệu (biến).

  • public thành viên có thể truy cập được bởi code bât kỳ đâu, ngoài đối tượng, không có hạn chế truy cập nào.
  • private phương thức, thuộc tính, trường khai báo với private chỉ có thể truy cập, gọi bởi các dòng code cùng lớp.
  • protected phương thức, thuộc tính, trường chỉ có thể truy cập, gọi bởi các dòng code cùng lớp hoặc các lớp kế thừa nó.
  • internal truy cập được bởi code ở cùng assembly (file).
  • protected internal truy cập được từ code assembly, hoặc lớp kế thừa nó ở assembly khác.
  • private protected truy cập được code khi cùng assembly trong cùng lớp, hoặc các lớp kế thừa nó.

Khi không chỉ rõ Modify thì mặc định là private cho phương thức, thuộc tính, trường. Assembly là file exe, dll

Ví dụ:

class Student
{
    private string Name;
}

Khi sử dụng

var s = new Student();
s.Name = "ABC";

Biên dịch sẽ lỗi error CS0122: 'Student.Name' is inaccessible due to its protection level. Vì trường Nameprivate không thể truy cập bằng code bên ngoài lớp như trên. Nhưng nếu thay bằng public thì không lỗi.

Khi lập trình cố gắng tối đa ẩn thông tin ra bên ngoài lớp càng nhiều càng tốt để đảm bảo tính đóng gói của kỹ thuật lập trình OOP, nó giúp cho code dễ bảo trì và giám sát lỗi.

Thuộc tính trong lớp

Trường dữ liệu của lớp

Khai báo các biến trong lớp, nó là thành viên của lớp, nó là biến. Trường dữ liệu có thể sử dụng bởi các phương thức trong lớp, hoặc nếu là public nó có thể truy cập từ bên ngoài, nhưng cách hay hơn để đảm bảo tính đóng gói khi cần truy cập thuộc tính hãy sử dụng phương thức, còn bản thân thuộc tính là private. Chúng ta đã sử dụng các trường dữ liệu ở những ví dụ trên.

Thuộc tính, bộ truy cập accessor setter/getter

Ngoài cách sử dụng trường dữ liệu, khai báo như biến ở phần trước, khai báo thuộc tính tương tự nhưng nó có cơ chế accessor (bộ truy cập), một cơ chế hết sức linh hoạt khi bạn đọc / ghi dữ liệu vào thuộc tính. Hãy tìm hiểu qua một ví dụ sau:

class Student
{
    private string name;
}

Lớp này có một trường dữ liệu private là name. Giờ ta sẽ khai báo một thuộc tính có tên Name với modify là public, thuộc tính này khi đọc sẽ thi hành một đoạn code gọi là get, khi ghi (gán) dữ liệu nó thi hành đoạn code gọi là set, thuộc tính Name sẽ phối hợp cùng trường dữ liệu name

class Student
{
    private string name;

    public string Name
    {
        // set thi hành khi gán, write
        // dữ liệu gán là value
        set
        {
            Console.WriteLine("Ghi dữ liệu <--" + value);
            name = value;
        }

        //get thi hành ghi đọc dữ liệu
        get {
            return "Tên là: " + name;
        }
    }
}

Khi thực hiện

var s = new Student();
s.Name = "XYZ";                                             // set thi hành
// In ra: Ghi dữ liệu <--XYZ
// Và trường name giờ bằng XYZ

Console.WriteLine(s.Name);                                  // get được thi hành
// In ra: Tên là: XYZ

Thuộc tính accessor có thể khai báo thiếu set hoặc get, nếu thiếu set nó trở thành loại chỉ đọc (readonly). Sử dụng set rất tiện lợi cho thao tác kiểm tra tính hợp lệ của dữ liệu khi gán, hoặc tự động thực hiện một số tác vụ mỗi khi dữ liệu được gán.

Bạn có thể khai báo một thuộc tính tự động, nó hoạt động giống như trường dữ liệu.

public string Name {set; get;}

Bạn lưu ý khai báo như trên nó là thuộc tính, bạn vẫn có thể khai báo như là trường:

public string Name;

Tuy nhiên khi không là thuộc tính, thì sẽ thiếu đi một số tính năng ứng dụng cao cấp sau này mà C# hỗ trợ.

Đăng ký theo dõi ủng hộ kênh