*** Ngoại lệ Exception
- Khi nghi ngờ hoặc dự đoán 1 đoạn code nào có thể phát sinh lỗi khiến chương
trình dừng lại => sử dụng try...catch... Trong đó:
try {
// code
}
catch {
// xử lý khi có lỗi
// VD: console.writeline("Có lỗi");
}
- Khi phát sinh lỗi trong try sẽ phát sinh 1 đối tượng Exception hoặc đối tg
đc kế thừa từ lớp Exception => có thể bắt lấy đối tượng đó để xử lý lỗi:
try {
// code
}
catch (Exception e) {
// console.log(e.Message);
// ....
}
Trong đó có các thuộc tính như:
e.Message: hiện ra tên lỗi
e.StackTrace: hiện ra nơi xảy ra lỗi
e.GetType().Name: tên kiểu cụ thể của đối tượng exception phát sinh
- Khi một đoạn code có thể phát sinh nhiều exception, sd nhiều khối catch để
bắt được tất cả những exception đó, nhưng phải chỉ ra kiểu cụ thể của exception
vd lỗi chia cho 0 thì phải định nghĩa kiểu: DivideByZeroException; lỗi truy
cập vào index lớn hơn độ dài của mảng thì định nghĩa kiểu: IndexOutOfRangeException
... vì nếu dùng kiểu Exception thì chỉ định nghĩa được 1 lần.
- Có thể tạo ra Exception mới bằng cách định nghĩa 1 class kế thừa từ lớp
Exception như sau:
public MyException : Exception {
public MyException () : base("Message") {
// để rỗng
};
}
*** Kiểu vô danh và dynamic
- Kiểu vô danh: khi khai báo đối tượng mà không khai báo kiểu dữ liệu cho nó
VD: Anonymous Type - đc dùng phổ biến trong LINQ
var myProfile = new {
name = "XuanThuLab",
age = 20,
skill = "ABC"
};
-> Nhưng khi dùng object có kiểu vô danh truyền làm tham số cho 1 mhàm ->
Lỗi -> Dùng kiểu `dynamic`
- Kiểu `dynamic`:
Giống với var, nhưng khác ở thời điểm xác định kiêu của biến.
+ var: ở thời điểm biên dịch
+ dynamic: ở thời điểm run
Có thể dùng kiểu động này với kiểu dữ liệu bất kì
*** null & nullable
- `null`: là một giá trị biểu hiện cho biến không tham chiếu đến đối tượng nào
-> KHÔNG CÓ GÌ -> KHÔNG TRỎ ĐẾN ĐỐI TƯỢNG NÀO
- `null` chỉ gán được cho các biến tham chiếu, KHÔNG gán được cho những biến
tham trị như int, float, bool...
- `nullable`:
+ Thêm dấu ? sau tên kiểu, giúp các biến có kiểu dữ liệu nguyên tố như int,
float, double có thể null, có thể sử dụng như đối tượng.
+ Khi muốn truy cập giá trị thì dùng `tên_biến.Value`'
*** Delegate
- Giúp tạo ra 1 kiểu dữ liệu mới, tham chiếu đến `các phương thức có cùng kiểu
trả về và tham số giống với delegate`. Syntax:
delegate Kiểu_trả_về tên_biến (tham_số);
Lưu ý: không cần thân hàm - khi gán hàm cho delegate chỉ cần gán tên hàm,
KHÔNG CẦN THAM SỐ VÀ CẶP NGOẶC ĐƠN.
VD:
public class Program
{
delegate void MyDelegate(int i); // Khai báo delegate
static void Main(string[] args)
{
void Show(int i)
{
Console.WriteLine("This is number " + i);
};
MyDelegate myDelegate = Show; // chỉ cần gán tên hàm
myDelegate(1);
}
}
- Có thể thực thi delegate bằng 2 cách:
+ Gọi tên biến và thêm (tham_số) vào sau
+ tên_biến.Invoke(tham_số)
- Muốn 1 delegate gọi tới nhiều function, sử dụng dấu +=
VD:
log += Infor;
log += Infor;
log += Infor;
log += Warning;
log += Warning;
log += Infor;
log.Invoke(message);
// Sẽ show ra Infor 3 lần, Warning 2 lần rồi lại show Infor 1 lần
- Có 2 kiểu delegate mà .NET đã định nghĩa sẵn: Action và Func, sử dụng tham số
generic để khai báo
+ Action: là định nghĩa của 1 delegate ko có kiểu trả về - void
Syntax:
Action<paramType> actionName;
VD: Action action; // ~ delegate void Kieu();
Action<string> action1; // ~ delegate void Kieu(string s);
Action<string, int> action2; // ~ delegate void Kieu(string s, int i);
+ Func: là định nghĩa của 1 delegate có kiểu trả về
Syntax:
Func<paramType.., returnType> funcName;
Note: khác với Action, phải khai báo kiểu trả về cho delegate bằng cách
để kiểu trả về ở tham số cuối cùng của Func, ở đây là returnType
*** Biểu thức lambda
- Giống anonymous function trong JS, có thể gán nó trực tiếp cho 1 delegate
- Cũng có thể sử dụng lamda để gán trực tiếp cho Action và Func
-- Có thể dùng lamda để định nghĩa phương thức có kiểu trả về khác void:
VD:
int Tong(int x, int y) => x + y;
*** Event
- Lớp gửi đi sự kiện đc gọi là publisher, lớp nhận sự kiện đó là subcriber
- Các sự kiện - event hoạt động giống với cơ chế của delegate. Trong .NET các
event đc xây dựng với nền tảng chính là delegate.
VD:
public delegate void SuKienNhapSo (int x);
public class UserInput
{
public SuKienNhapSo suKienNhapSo { get; set; }
public void Input()
{
do
{
try
{
int i = Int32.Parse(Console.ReadLine());
suKienNhapSo?.Invoke(i);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
} while (true);
}
}
public class BinhPhuong
{
public void NhanSuKien (UserInput input)
{
input.suKienNhapSo = TinhBinhPhuong;
}
public void TinhBinhPhuong (int x)
{
Console.WriteLine($"Binh phuong cua {x} la {x*x}");
}
}
public class Program
{
static void Main(string[] args)
{
UserInput input = new UserInput();
BinhPhuong binhPhuong = new BinhPhuong();
binhPhuong.NhanSuKien(input);
input.Input();
}
}
- Tại sao có delegate rồi mà còn phải có event?
+ Vì khi dùng 2 subcriber để đăng ký nhận event từ Publisher, sẽ bị hủy đi
các subcriber đằng trước và chỉ lấy subscriber mới nhất -> chỉ 1 subcriber
mới nhận được event
-> phá hỏng nguyên tắc hoạt động của mô hình hướng sự kiện - phá vỡ sự đóng
gói
=> Để giải quyết vấn đề trên, thêm từ khóa `event` vào trước tên kiểu delegate
khi khai báo, lúc này delegate sẽ:
+ Đc gọi là event, not delegate
+ Chỉ có thể được += hoặc -=, ko thể gán =
+ Lúc này biến được khai báo vs từ khóa event sẽ là trường dữ liệu, ko thể
là thuộc tính.
- Cuối cùng, nên sử dụng delegate có sẵn của C# là EvenHandler để xử lý các sự
kiện cho đồng bộ, tránh khai báo các delegate riêng lẻ.
Syntax:
public event EvenHandler name;
// ~ delegate void KIEU(object sender, EventArgs args)
// Trong đó: cần xây dựng các lớp kế thừa từ EventArgs để thêm vào các
// tham số riêng khi gửi sự kiện
Note nhỏ: bản thân các biến kiểu object đã hỗ trợ sẵn gán biến null
*** Phương thức mở rộng
- Các phương thức mở rộng là các phương thức thêm vào class, struct, interface
có sẵn mà không cần thiết phải kế thừa lại class để tạo ra class mới, ko cần
biên dịch lại thư viện
- Cách thêm 1 phương thức vào 1 lớp:
VD: Có 1 method static:
public static void Print(string s, ConsoleColor color =
ConsoleColor.Yellow)
{
ConsoleColor lastColor = Console.ForegroundColor;
Console.ForegroundColor = color;
Console.WriteLine(s);
Console.ForegroundColor = lastColor;
}
Để thêm phương thức vào lớp string:
+ Chuyển khai báo Print trong một lớp tĩnh
+ Đảm bảo Print là phương thức tĩnh
+ Tham số đầu tiên của hàm là kiểu string (lớp mà phương thức mở rộng
sẽ thêm vào) cho thêm từ khóa this vào phía trước kiểu này - để cho
biết sẽ mở rộng lớp string với phương thức này.
=> public static class MyExtensionMethods {
public static void Print(this string s, ConsoleColor color =
ConsoleColor.Yellow)
{
ConsoleColor lastColor = Console.ForegroundColor;
Console.ForegroundColor = color;
Console.WriteLine(s);
Console.ForegroundColor = lastColor;
}
}
*** Các tính chất OOP
** Tính đóng gói
- Là tính chất hạn chế các thành phần bên ngoài tác động vào thuộc tính
hoặc thi hành các phương thức nội bộ của 1 class. Bên ngoài chỉ truy
cập và sd đc những thành phần nào mà người lập trình cho phép.
- C# triển khai tính đóng gói thông qua các access modifier:
+ private: các props và methods chỉ có thể đc truy cập bởi các
thành
phần bên trong class đó
+ public: các props và methods có thể đc truy cập ở mọi nơi
+ protected: các props và methods chỉ có thể đc truy cập bởi các
thành phần bên trong class đó và các class kế thừa class đó
+ internal: các props và methods có thể đc truy cập ở trong cùng
1 assembly (file exe, dll)
+ protected internal: cùng assembly, hoặc các class kế thừa nó ở
các assembly khác
+ private protected: cùng assembly trong cùng code, hoặc các lớp
kế
thừa nó.
Lưu ý: nếu không chỉ rõ Modify thì mặc định là private cho phương
thức, thuộc tính, trường
- Hoặc thông qua bộ truy cập accessor getter/setter:
set là gán giá trị cho thuộc tính
get là lấy giá trị của thuộc tính
** Tính kế thừa
- Một lớp kế thừa là có thể sở hữu những thuộc tính và phương thức từ
lớp mà nó được kế thừa (lớp cơ sở)
- Để 1 lớp ko bị kế thừa bởi lớp khác -> thêm từ khóa `sealed` trước
từ khóa class
** Tính đa hình
* Định nghĩa:
- Nghĩa là có nhiều hình dạng, thể hiện rõ khi xây dựng các lớp
kế thừa
-> Các class con có thể override các methods kế thừa từ class cha
-> Với mỗi đối tượng khác nhau thì phương thức sẽ khác nhau
-> Phát biểu: Cùng 1 class, nếu có các đối tượng khác nhau với
thuộc
tính khác nhau thì sẽ có các phương thức khác nhau -> Đó là tính
đa hình
-> Phần quá tải phương thức đã thể hiện rõ tính đa hình
* Phương thức ảo: có thể bị ghi đè bởi lớp kế thừa, thêm từ khóa
virtual
trc kiểu trả về của method. VD:
public virtual void ProductInfo() {
Console.WriteLine($"Giá sản phẩm {price}");
}
* Ghi đè phương thức: thêm override trước kiểu trả về của method:
public override void ProductInfo() {
Console.WriteLine($"Điện thoại Iphone");
base.ProductInfo();
}
// Muốn gọi đến method ở class cha, dùng `base.method_Name`
** Tính trừu tượng
* Định nghĩa:
- Tổng quát hóa 1 công việc thành 1 cái tên nào đó cho biết sẽ
thực hiện
cv đó mà ko biết bên trong có gì. =>??? :D
-> Đối với method: Khi thiết kế 1 method thành abstract, phương
thức đó
chỉ có tên và tham số chứ không có thân hàm. Và class con kế thừa
lớp cha
của method đó bắt buộc phải ghi đè method đó.
-> Đối với class: Khi thiết kế 1 class thành abstract, ta không
thể khởi
tạo đối tượng từ class đó -> chỉ làm lớp cơ sở để lớp khác kế
thừa
* Interface: gần giống với abstract class, khác:
+ Khi khai báo dùng từ khóa interface thay cho abstract class
+ Bắt buộc phải ghi đè lại khi class khác kế thừa (ko cần
override)
+ 1 class thì kế thừa được nhìu interface, còn abstract thì chỉ 1
*** Hàm hủy - Quá tải toán tử - Thành viên tĩnh - Indexer
- Hàm hủy - Finalizer/Destructor
+ Syntax: thêm dấu ~ vào trước tên hàm khởi tạo -> hàm hủy:
public class MyClass {
~MyClass () {
// đây là hàm hủy
}
}
+ Được dùng khi có nhu cầu dọn dẹp, giải phóng tài nguyên chiếm giữ
+ Khi đối tượng bị giải phóng -> tự động thi hành 1 phương thức gọi là
phương thức hủy (Finalizer - Destructor)
+ Lưu ý:
++ 1 lớp chỉ có 1 hàm hủy
++ Không thể gọi hàm hủy chủ động -> hệ thống .NET tự quyết định thi
hành nó khi nào thông qua 1 service là Garbage Collector. Vậy khi nào
thì nó đc .NET - GC gọi?
-> Tự động đc gọi khi thấy thiếu bộ nhớ hoặc bộ nhớ do .NET cấp
phát trên HEAP ko còn đc dùng đến.
-> HEAP là bộ nhớ lưu các đối tượng đc tạo ra từ các class bằng
toán tử new. Khi đối tượng đó ko còn đc biến nào tham chiếu đến
thì nó sẽ được đánh dấu thu hồi bởi GC-> Lúc nào GC chạy (do .NET
quyết định) -> Hàm hủy sẽ đc thi hành
-> Có thể yêu cầu .NET làm điều đó bằng GC.Collect();
- Quá tải toán tử - Operator Overloading
+ Giúp tạo ra các phép toán giữa các đối tượng của 1 class với nhau.
+ Syntax:
Pham_vi_truy_cap static Kieu_tra_ve operatorOperator_symbol (param1,
param2){...};
VD: có 1 class Vector có 2 thuộc tính x,y là tọa độ, xây dựng toán tử
cộng 2 đối tượng vector lại để ra vector thứ 3:
public static Vector operator+ (Vector A, Vector B) {
return new Vector {
x = A.x + B.x,
y = A.y + B.y
};
} => tadaa
- Thành viên tĩnh - Static Variable/Method:
+ Không thuộc về đối tượng cụ thể nào, có thể sd mà ko cần tạo đối tượng
Truy cập: Tên_class.Tên_thành_viên_tĩnh
+ Có 2 loại thành viên tĩnh:
++ Biến
++ Phương thức
+ Chỉ khởi tạo 1 lần duy nhất, dùng cho mọi đối tượng thuộc class
+ Dùng static constructor để khởi tạo cho các thành viên tĩnh. Constructor
này sẽ được gọi khi lần đầu TRUY CẬP vào thành viên tĩnh.
- Lớp tĩnh - Static Class:
+ Các thành viên trong lớp tĩnh đều là static
-> thường đc dùng để gom các hàm tiện ích lại với nhau.
VD: class Math là class tĩnh có các phương thức tĩnh như Math.Abs(),
Math.Sin()....
- Thành viên chỉ đọc `readonly`: gần giống với const, khác:
+ Const phải đc gán giá trị ngay khi khởi tạo, readonly thì khum
+ Biến readonly có thể đc gán giá trị trong constructor
-> Giống: đều không thể thay đổi giá trị sau khi được gán
- Bộ đánh chỉ mục Indexer:
+ Có tác dụng đánh index cho các thành viên của lớp
+ Syntax:
public kiểu_trả_về this[kiểu_index index]
{
get {
// thực hiện các tác vụ và trả về dữ liệu có
kiểu_trả_về
}
set {
// giá trị được truyền trong biến value, có thể lưu
nó vào nơi thích hợp
}
}
*** Cấu trúc dữ liệu
1. List
- Là 1 tập hợp các dữ liệu có cùng kiểu, có thể thay đổi số lượng phần tử tùy
ý, ko cố định như mảng.
- Để sd Kiểu dữ liệu List cần dùng thư viện System.Collections.Generic
- Có thể thêm một mảng các array vào List
2. ShortedList
- Là kiểu dữ liệu dạng danh sách, nhưng mỗi phần tử là cặp key - value
- VD:
SortedList<string, Product> mySortedList = new SortedList<string,
Product>();
// Add thêm vào sortedList
mySortedList.Add(keyName, Product); // Khai báo hẳn key và Product ra
nhé, lười
// khum muốn viết ra
// hoặc
mySortedList[key] = new Product {...}
3. Queue - Hàng đợi
- Queue - cấu trúc dl kiểu hàng đợi, tương tự như List, bao gồm các phương thức
của chỉ khác là khi thêm phần tử thì auto thêm vào cuối, khi xóa thì xóa ở đầu,
bao gồm các phương thức chính:
+ Enqueue: Thêm phần tử vào cuối hàng đợi - Vào xếp hàng
+ Dequeue: Xóa bỏ phần tử ở đầu hàng đợi và trả về chính phần tử đó
+ Count: Đếm số phần tử trong hàng đợi
+ Peek: lấy ra phần tử đầu hàng đợi
4. Stack - Ngăn xếp
- Giống như 1 cái hộp, cái nào xếp vào đầu tiên -> ở cuối cùng, xếp vào cuối cùng
-> ở trên đầu
- Các phương thức chính:
+ Push: thêm 1 phần tử vào đỉnh của ngăn xếp
+ Pop: xóa phần tử ở đỉnh và trả về phần tử đóng
+ Peek: đọc phần tử ở đỉnh
+ Contains: kiểm tra 1 phần tử có ở trong ngăn xếp khum
+ Count: đếm số phần tử trong ngăn xếp
5. LinkedList - Danh sách liên kết kép (gọi tắt là dslk)
- DSLK: mỗi phần tử trong dslk là 1 nút - node, mỗi nút ngoài dữ liệu của nó
sẽ bao gồm 2 biến:
+ 1 biến tham chiếu đến phía trc
+ 1 biến tham chiếu đến phía sau
- Lưu ý: bản thân các node có kiểu dữ liệu là LinkedListNode, có các thuộc
tính chính sau:
+ List: trỏ đến LinkedList
+ Value: dữ liệu của Node
+ Next: trỏ đến node típ theo, nếu nó là nốt cuối => null
+ Previous: trỏ đến node đằng trc, nếu nó là node đầu => null
Count Số nút trong danh sách
First Nút đầu tiên của danh sách
Last Nút đầu tiên của danh sách
AddFirst(T) Chèn một nút có dữ liệu T vào đầu danh sách
AddLast(T) Chèn một nút có dữ liệu T vào cuối danh sách
AddAfter(Node, T) Chèn nút với dữ liệu T, vào sau nút Node (kiểu
LinkedListNode)
AddBefore(Node, T) Chèn nút với dữ liệu T, vào trước nút Node
(kiểu LinkedListNode)
Clear() Xóa toàn bộ danh sách
Contains(T) Kiểm tra xem có nút với giá trị dữ liệu bằng T
Remove(T) Xóa nút có dữ liệu bằng T
RemoveFirst() Xóa nút đầu tiên
RemoveLast() Xóa nút cuối cùng
Find(T) Tìm một nút
First Nút đầu tiên của danh sách
Last Nút đầu tiên của danh sách
AddFirst(T) Chèn một nút có dữ liệu T vào đầu danh sách
AddLast(T) Chèn một nút có dữ liệu T vào cuối danh sách
AddAfter(Node, T) Chèn nút với dữ liệu T, vào sau nút Node (kiểu
LinkedListNode)
AddBefore(Node, T) Chèn nút với dữ liệu T, vào trước nút Node
(kiểu LinkedListNode)
Clear() Xóa toàn bộ danh sách
Contains(T) Kiểm tra xem có nút với giá trị dữ liệu bằng T
Remove(T) Xóa nút có dữ liệu bằng T
RemoveFirst() Xóa nút đầu tiên
RemoveLast() Xóa nút cuối cùng
Find(T) Tìm một nút
6. Dictionary
- Khá giống SortedList, nhưng đc dùng cho tập dữ liệu lớn
- Có các phương thức và cách sử dụng không khác gì SortedList
7. HashSet
- Là tập hợp danh sách không cho phép trùng giá trị. Khác với các collection
khác, nó cung cấp cơ chế đơn giản nhất để lưu trữ giá trị: không chỉ mục thứ
tự, không sắp xếp theo thứ tự nào
=> Cung cấp hiệu năng cao cho các tác vụ tìm kiếm, thêm vào, xóa bỏ...
*** Lập trình bất đồng bộ
- Có thể dùng Thread hoặc Task để lập trình bất đồng bộ trong C#
- lock: dùng để khóa 1 biến, đối tượng nào đó cho đến khi luồng sd biến, đối
tượng đó chạy xong thì luồng khác (cũng dùng đến biến, đối tượng đó) mới được
chạy tiếp -> đảm bảo lập trình đồng bộ
- .NET cho phép tạo ra nhiều tác vụ chạy song song với nhau, trên các luồng
khác nhau. Lớp để biểu diễn tác vụ gồm Task và Task<T>. Syntax:
+ Task: ko có kiểu trả về, tức là kiểu void => delegate Action
++ Task task = new Task(Action);
++ Task task = new task(Action(Object), Object);
Trong đó:
Action(Object) tương đương delegate: (Object obj) => {}
Object: tham số truyền vào cho obj ở trên
- Lưu ý: Khi các Task chạy trên các thread khác nhau, đến dòng cuối
cùng của
hàm mà vẫn có Task khác chưa chạy xong => đóng luôn
+ Task<T>: có kiểu trả về
Syntax:
Task<T> task = new Task<T>(myfunc);
*** LINQ - Language Integrated Query
- Nguồn dữ liệu có thể sử dụng LINQ: IEnumrable, IEnumrable<T> (array, list,
stack, queue...), file XML, các csdl SQL được nạp vào chương trình đc thể
hiện thông qua các đối tượng lớp List, Stack...
- Một số phương thức thông dụng:
+ Select
+ Where
+ SelectMany: chuyển đổi tất cả các phần tử trong các danh sách, mà danh sách
đó là
thuộc tính của 1 phần tử của 1 danh sách các đối tượng => thành một danh sách
duy nhất

Preview text:

*** Ngoại lệ Exception
- Khi nghi ngờ hoặc dự đoán 1 đoạn code nào có thể phát sinh lỗi khiến chương
trình dừng lại => sử dụng try...catch... Trong đó: try { // code } catch { // xử lý khi có lỗi
// VD: console.writeline("Có lỗi"); }
- Khi phát sinh lỗi trong try sẽ phát sinh 1 đối tượng Exception hoặc đối tg
đc kế thừa từ lớp Exception => có thể bắt lấy đối tượng đó để xử lý lỗi: try { // code } catch (Exception e) { // console.log(e.Message); // .... }
Trong đó có các thuộc tính như:
e.Message: hiện ra tên lỗi
e.StackTrace: hiện ra nơi xảy ra lỗi
e.GetType().Name: tên kiểu cụ thể của đối tượng exception phát sinh
- Khi một đoạn code có thể phát sinh nhiều exception, sd nhiều khối catch để
bắt được tất cả những exception đó, nhưng phải chỉ ra kiểu cụ thể của exception
vd lỗi chia cho 0 thì phải định nghĩa kiểu: DivideByZeroException; lỗi truy
cập vào index lớn hơn độ dài của mảng thì định nghĩa kiểu: IndexOutOfRangeException
... vì nếu dùng kiểu Exception thì chỉ định nghĩa được 1 lần.
- Có thể tạo ra Exception mới bằng cách định nghĩa 1 class kế thừa từ lớp Exception như sau:
public MyException : Exception {
public MyException () : base("Message") { // để rỗng }; }
*** Kiểu vô danh và dynamic
- Kiểu vô danh: khi khai báo đối tượng mà không khai báo kiểu dữ liệu cho nó
VD: Anonymous Type - đc dùng phổ biến trong LINQ var myProfile = new { name = "XuanThuLab", age = 20, skill = "ABC" };
-> Nhưng khi dùng object có kiểu vô danh truyền làm tham số cho 1 mhàm ->
Lỗi -> Dùng kiểu `dynamic` - Kiểu `dynamic`:
Giống với var, nhưng khác ở thời điểm xác định kiêu của biến.
+ var: ở thời điểm biên dịch
+ dynamic: ở thời điểm run
Có thể dùng kiểu động này với kiểu dữ liệu bất kì *** null & nullable
- `null`: là một giá trị biểu hiện cho biến không tham chiếu đến đối tượng nào
-> KHÔNG CÓ GÌ -> KHÔNG TRỎ ĐẾN ĐỐI TƯỢNG NÀO
- `null` chỉ gán được cho các biến tham chiếu, KHÔNG gán được cho những biến
tham trị như int, float, bool... - `nullable`:
+ Thêm dấu ? sau tên kiểu, giúp các biến có kiểu dữ liệu nguyên tố như int,
float, double có thể null, có thể sử dụng như đối tượng.
+ Khi muốn truy cập giá trị thì dùng `tên_biến.Value`' *** Delegate
- Giúp tạo ra 1 kiểu dữ liệu mới, tham chiếu đến `các phương thức có cùng kiểu
trả về và tham số giống với delegate`. Syntax:
delegate Kiểu_trả_về tên_biến (tham_số);
Lưu ý: không cần thân hàm - khi gán hàm cho delegate chỉ cần gán tên hàm,
KHÔNG CẦN THAM SỐ VÀ CẶP NGOẶC ĐƠN. VD: public class Program {
delegate void MyDelegate(int i); // Khai báo delegate
static void Main(string[] args) { void Show(int i) {
Console.WriteLine("This is number " + i); };
MyDelegate myDelegate = Show; // chỉ cần gán tên hàm myDelegate(1); } }
- Có thể thực thi delegate bằng 2 cách:
+ Gọi tên biến và thêm (tham_số) vào sau
+ tên_biến.Invoke(tham_số)
- Muốn 1 delegate gọi tới nhiều function, sử dụng dấu += VD: log += Infor; log += Infor; log += Infor; log += Warning; log += Warning; log += Infor; log.Invoke(message);
// Sẽ show ra Infor 3 lần, Warning 2 lần rồi lại show Infor 1 lần
- Có 2 kiểu delegate mà .NET đã định nghĩa sẵn: Action và Func, sử dụng tham số generic để khai báo
+ Action: là định nghĩa của 1 delegate ko có kiểu trả về - void Syntax: Action actionName; VD:
Action action; // ~ delegate void Kieu();
Action action1; // ~ delegate void Kieu(string s);
Action action2; // ~ delegate void Kieu(string s, int i);
+ Func: là định nghĩa của 1 delegate có kiểu trả về Syntax: Func funcName;
Note: khác với Action, phải khai báo kiểu trả về cho delegate bằng cách
để kiểu trả về ở tham số cuối cùng của Func, ở đây là returnType *** Biểu thức lambda
- Giống anonymous function trong JS, có thể gán nó trực tiếp cho 1 delegate
- Cũng có thể sử dụng lamda để gán trực tiếp cho Action và Func
-- Có thể dùng lamda để định nghĩa phương thức có kiểu trả về khác void: VD:
int Tong(int x, int y) => x + y; *** Event
- Lớp gửi đi sự kiện đc gọi là publisher, lớp nhận sự kiện đó là subcriber
- Các sự kiện - event hoạt động giống với cơ chế của delegate. Trong .NET các
event đc xây dựng với nền tảng chính là delegate. VD:
public delegate void SuKienNhapSo (int x); public class UserInput {
public SuKienNhapSo suKienNhapSo { get; set; } public void Input() { do { try {
int i = Int32.Parse(Console.ReadLine()); suKienNhapSo?.Invoke(i); } catch (Exception e) { Console.WriteLine(e.Message); } } while (true); } } public class BinhPhuong {
public void NhanSuKien (UserInput input) {
input.suKienNhapSo = TinhBinhPhuong; }
public void TinhBinhPhuong (int x) {
Console.WriteLine($"Binh phuong cua {x} la {x*x}"); } } public class Program {
static void Main(string[] args) {
UserInput input = new UserInput();
BinhPhuong binhPhuong = new BinhPhuong(); binhPhuong.NhanSuKien(input); input.Input(); } }
- Tại sao có delegate rồi mà còn phải có event?
+ Vì khi dùng 2 subcriber để đăng ký nhận event từ Publisher, sẽ bị hủy đi
các subcriber đằng trước và chỉ lấy subscriber mới nhất -> chỉ 1 subcriber mới nhận được event
-> phá hỏng nguyên tắc hoạt động của mô hình hướng sự kiện - phá vỡ sự đóng gói
=> Để giải quyết vấn đề trên, thêm từ khóa `event` vào trước tên kiểu delegate
khi khai báo, lúc này delegate sẽ:
+ Đc gọi là event, not delegate
+ Chỉ có thể được += hoặc -=, ko thể gán =
+ Lúc này biến được khai báo vs từ khóa event sẽ là trường dữ liệu, ko thể là thuộc tính.
- Cuối cùng, nên sử dụng delegate có sẵn của C# là EvenHandler để xử lý các sự
kiện cho đồng bộ, tránh khai báo các delegate riêng lẻ. Syntax: public event EvenHandler name;
// ~ delegate void KIEU(object sender, EventArgs args)
// Trong đó: cần xây dựng các lớp kế thừa từ EventArgs để thêm vào các
// tham số riêng khi gửi sự kiện
Note nhỏ: bản thân các biến kiểu object đã hỗ trợ sẵn gán biến null
*** Phương thức mở rộng
- Các phương thức mở rộng là các phương thức thêm vào class, struct, interface
có sẵn mà không cần thiết phải kế thừa lại class để tạo ra class mới, ko cần biên dịch lại thư viện
- Cách thêm 1 phương thức vào 1 lớp: VD: Có 1 method static:
public static void Print(string s, ConsoleColor color = ConsoleColor.Yellow) {
ConsoleColor lastColor = Console.ForegroundColor;
Console.ForegroundColor = color; Console.WriteLine(s);
Console.ForegroundColor = lastColor; }
Để thêm phương thức vào lớp string:
+ Chuyển khai báo Print trong một lớp tĩnh
+ Đảm bảo Print là phương thức tĩnh
+ Tham số đầu tiên của hàm là kiểu string (lớp mà phương thức mở rộng
sẽ thêm vào) cho thêm từ khóa this vào phía trước kiểu này - để cho
biết sẽ mở rộng lớp string với phương thức này. =>
public static class MyExtensionMethods {
public static void Print(this string s, ConsoleColor color = ConsoleColor.Yellow) {
ConsoleColor lastColor = Console.ForegroundColor;
Console.ForegroundColor = color; Console.WriteLine(s);
Console.ForegroundColor = lastColor; } } *** Các tính chất OOP ** Tính đóng gói
- Là tính chất hạn chế các thành phần bên ngoài tác động vào thuộc tính
hoặc thi hành các phương thức nội bộ của 1 class. Bên ngoài chỉ truy
cập và sd đc những thành phần nào mà người lập trình cho phép.
- C# triển khai tính đóng gói thông qua các access modifier:
+ private: các props và methods chỉ có thể đc truy cập bởi các thành phần bên trong class đó
+ public: các props và methods có thể đc truy cập ở mọi nơi
+ protected: các props và methods chỉ có thể đc truy cập bởi các
thành phần bên trong class đó và các class kế thừa class đó
+ internal: các props và methods có thể đc truy cập ở trong cùng 1 assembly (file exe, dll)
+ protected internal: cùng assembly, hoặc các class kế thừa nó ở các assembly khác
+ private protected: cùng assembly trong cùng code, hoặc các lớp kế thừa nó.
Lưu ý: nếu không chỉ rõ Modify thì mặc định là private cho phương
thức, thuộc tính, trường
- Hoặc thông qua bộ truy cập accessor getter/setter:
set là gán giá trị cho thuộc tính
get là lấy giá trị của thuộc tính ** Tính kế thừa
- Một lớp kế thừa là có thể sở hữu những thuộc tính và phương thức từ
lớp mà nó được kế thừa (lớp cơ sở)
- Để 1 lớp ko bị kế thừa bởi lớp khác -> thêm từ khóa `sealed` trước từ khóa class ** Tính đa hình * Định nghĩa:
- Nghĩa là có nhiều hình dạng, thể hiện rõ khi xây dựng các lớp kế thừa
-> Các class con có thể override các methods kế thừa từ class cha
-> Với mỗi đối tượng khác nhau thì phương thức sẽ khác nhau
-> Phát biểu: Cùng 1 class, nếu có các đối tượng khác nhau với thuộc
tính khác nhau thì sẽ có các phương thức khác nhau -> Đó là tính đa hình
-> Phần quá tải phương thức đã thể hiện rõ tính đa hình
* Phương thức ảo: có thể bị ghi đè bởi lớp kế thừa, thêm từ khóa virtual
trc kiểu trả về của method. VD:
public virtual void ProductInfo() {
Console.WriteLine($"Giá sản phẩm {price}"); }
* Ghi đè phương thức: thêm override trước kiểu trả về của method:
public override void ProductInfo() {
Console.WriteLine($"Điện thoại Iphone"); base.ProductInfo(); }
// Muốn gọi đến method ở class cha, dùng `base.method_Name` ** Tính trừu tượng * Định nghĩa:
- Tổng quát hóa 1 công việc thành 1 cái tên nào đó cho biết sẽ thực hiện
cv đó mà ko biết bên trong có gì. =>??? :D
-> Đối với method: Khi thiết kế 1 method thành abstract, phương thức đó
chỉ có tên và tham số chứ không có thân hàm. Và class con kế thừa lớp cha
của method đó bắt buộc phải ghi đè method đó.
-> Đối với class: Khi thiết kế 1 class thành abstract, ta không thể khởi
tạo đối tượng từ class đó -> chỉ làm lớp cơ sở để lớp khác kế thừa
* Interface: gần giống với abstract class, khác:
+ Khi khai báo dùng từ khóa interface thay cho abstract class
+ Bắt buộc phải ghi đè lại khi class khác kế thừa (ko cần override)
+ 1 class thì kế thừa được nhìu interface, còn abstract thì chỉ 1
*** Hàm hủy - Quá tải toán tử - Thành viên tĩnh - Indexer
- Hàm hủy - Finalizer/Destructor
+ Syntax: thêm dấu ~ vào trước tên hàm khởi tạo -> hàm hủy: public class MyClass { ~MyClass () { // đây là hàm hủy } }
+ Được dùng khi có nhu cầu dọn dẹp, giải phóng tài nguyên chiếm giữ
+ Khi đối tượng bị giải phóng -> tự động thi hành 1 phương thức gọi là
phương thức hủy (Finalizer - Destructor) + Lưu ý:
++ 1 lớp chỉ có 1 hàm hủy
++ Không thể gọi hàm hủy chủ động -> hệ thống .NET tự quyết định thi
hành nó khi nào thông qua 1 service là Garbage Collector. Vậy khi nào thì nó đc .NET - GC gọi?
-> Tự động đc gọi khi thấy thiếu bộ nhớ hoặc bộ nhớ do .NET cấp
phát trên HEAP ko còn đc dùng đến.
-> HEAP là bộ nhớ lưu các đối tượng đc tạo ra từ các class bằng
toán tử new. Khi đối tượng đó ko còn đc biến nào tham chiếu đến
thì nó sẽ được đánh dấu thu hồi bởi GC-> Lúc nào GC chạy (do .NET
quyết định) -> Hàm hủy sẽ đc thi hành
-> Có thể yêu cầu .NET làm điều đó bằng GC.Collect();
- Quá tải toán tử - Operator Overloading
+ Giúp tạo ra các phép toán giữa các đối tượng của 1 class với nhau. + Syntax:
Pham_vi_truy_cap static Kieu_tra_ve operatorOperator_symbol (param1, param2){...};
VD: có 1 class Vector có 2 thuộc tính x,y là tọa độ, xây dựng toán tử
cộng 2 đối tượng vector lại để ra vector thứ 3:
public static Vector operator+ (Vector A, Vector B) { return new Vector { x = A.x + B.x, y = A.y + B.y }; } => tadaa
- Thành viên tĩnh - Static Variable/Method:
+ Không thuộc về đối tượng cụ thể nào, có thể sd mà ko cần tạo đối tượng
Truy cập: Tên_class.Tên_thành_viên_tĩnh
+ Có 2 loại thành viên tĩnh: ++ Biến ++ Phương thức
+ Chỉ khởi tạo 1 lần duy nhất, dùng cho mọi đối tượng thuộc class
+ Dùng static constructor để khởi tạo cho các thành viên tĩnh. Constructor
này sẽ được gọi khi lần đầu TRUY CẬP vào thành viên tĩnh. - Lớp tĩnh - Static Class:
+ Các thành viên trong lớp tĩnh đều là static
-> thường đc dùng để gom các hàm tiện ích lại với nhau.
VD: class Math là class tĩnh có các phương thức tĩnh như Math.Abs(), Math.Sin()....
- Thành viên chỉ đọc `readonly`: gần giống với const, khác:
+ Const phải đc gán giá trị ngay khi khởi tạo, readonly thì khum
+ Biến readonly có thể đc gán giá trị trong constructor
-> Giống: đều không thể thay đổi giá trị sau khi được gán
- Bộ đánh chỉ mục Indexer:
+ Có tác dụng đánh index cho các thành viên của lớp + Syntax:
public kiểu_trả_về this[kiểu_index index] { get {
// thực hiện các tác vụ và trả về dữ liệu có kiểu_trả_về } set {
// giá trị được truyền trong biến value, có thể lưu nó vào nơi thích hợp } } *** Cấu trúc dữ liệu 1. List
- Là 1 tập hợp các dữ liệu có cùng kiểu, có thể thay đổi số lượng phần tử tùy
ý, ko cố định như mảng.
- Để sd Kiểu dữ liệu List cần dùng thư viện System.Collections.Generic
- Có thể thêm một mảng các array vào List 2. ShortedList
- Là kiểu dữ liệu dạng danh sách, nhưng mỗi phần tử là cặp key - value - VD:
SortedList mySortedList = new SortedListProduct>(); // Add thêm vào sortedList
mySortedList.Add(keyName, Product); // Khai báo hẳn key và Product ra nhé, lười // khum muốn viết ra // hoặc
mySortedList[key] = new Product {...} 3. Queue - Hàng đợi
- Queue - cấu trúc dl kiểu hàng đợi, tương tự như List, bao gồm các phương thức
của chỉ khác là khi thêm phần tử thì auto thêm vào cuối, khi xóa thì xóa ở đầu,
bao gồm các phương thức chính:
+ Enqueue: Thêm phần tử vào cuối hàng đợi - Vào xếp hàng
+ Dequeue: Xóa bỏ phần tử ở đầu hàng đợi và trả về chính phần tử đó
+ Count: Đếm số phần tử trong hàng đợi
+ Peek: lấy ra phần tử đầu hàng đợi 4. Stack - Ngăn xếp
- Giống như 1 cái hộp, cái nào xếp vào đầu tiên -> ở cuối cùng, xếp vào cuối cùng -> ở trên đầu
- Các phương thức chính:
+ Push: thêm 1 phần tử vào đỉnh của ngăn xếp
+ Pop: xóa phần tử ở đỉnh và trả về phần tử đóng
+ Peek: đọc phần tử ở đỉnh
+ Contains: kiểm tra 1 phần tử có ở trong ngăn xếp khum
+ Count: đếm số phần tử trong ngăn xếp
5. LinkedList - Danh sách liên kết kép (gọi tắt là dslk)
- DSLK: mỗi phần tử trong dslk là 1 nút - node, mỗi nút ngoài dữ liệu của nó sẽ bao gồm 2 biến:
+ 1 biến tham chiếu đến phía trc
+ 1 biến tham chiếu đến phía sau
- Lưu ý: bản thân các node có kiểu dữ liệu là LinkedListNode, có các thuộc tính chính sau:
+ List: trỏ đến LinkedList
+ Value: dữ liệu của Node
+ Next: trỏ đến node típ theo, nếu nó là nốt cuối => null
+ Previous: trỏ đến node đằng trc, nếu nó là node đầu => null Count Số nút trong danh sách First
Nút đầu tiên của danh sách Last
Nút đầu tiên của danh sách AddFirst(T)
Chèn một nút có dữ liệu T vào đầu danh sách AddLast(T)
Chèn một nút có dữ liệu T vào cuối danh sách
AddAfter(Node, T) Chèn nút với dữ liệu T, vào sau nút Node (kiểu LinkedListNode) AddBefore(Node, T)
Chèn nút với dữ liệu T, vào trước nút Node (kiểu LinkedListNode) Clear() Xóa toàn bộ danh sách Contains(T)
Kiểm tra xem có nút với giá trị dữ liệu bằng T Remove(T)
Xóa nút có dữ liệu bằng T RemoveFirst() Xóa nút đầu tiên RemoveLast() Xóa nút cuối cùng Find(T) Tìm một nút
First Nút đầu tiên của danh sách Last
Nút đầu tiên của danh sách
AddFirst(T) Chèn một nút có dữ liệu T vào đầu danh sách
AddLast(T) Chèn một nút có dữ liệu T vào cuối danh sách
AddAfter(Node, T) Chèn nút với dữ liệu T, vào sau nút Node (kiểu LinkedListNode) AddBefore(Node, T)
Chèn nút với dữ liệu T, vào trước nút Node (kiểu LinkedListNode) Clear() Xóa toàn bộ danh sách
Contains(T) Kiểm tra xem có nút với giá trị dữ liệu bằng T Remove(T)
Xóa nút có dữ liệu bằng T RemoveFirst() Xóa nút đầu tiên RemoveLast() Xóa nút cuối cùng Find(T) Tìm một nút 6. Dictionary
- Khá giống SortedList, nhưng đc dùng cho tập dữ liệu lớn
- Có các phương thức và cách sử dụng không khác gì SortedList 7. HashSet
- Là tập hợp danh sách không cho phép trùng giá trị. Khác với các collection
khác, nó cung cấp cơ chế đơn giản nhất để lưu trữ giá trị: không chỉ mục thứ
tự, không sắp xếp theo thứ tự nào
=> Cung cấp hiệu năng cao cho các tác vụ tìm kiếm, thêm vào, xóa bỏ...
*** Lập trình bất đồng bộ
- Có thể dùng Thread hoặc Task để lập trình bất đồng bộ trong C#
- lock: dùng để khóa 1 biến, đối tượng nào đó cho đến khi luồng sd biến, đối
tượng đó chạy xong thì luồng khác (cũng dùng đến biến, đối tượng đó) mới được
chạy tiếp -> đảm bảo lập trình đồng bộ
- .NET cho phép tạo ra nhiều tác vụ chạy song song với nhau, trên các luồng
khác nhau. Lớp để biểu diễn tác vụ gồm Task và Task. Syntax:
+ Task: ko có kiểu trả về, tức là kiểu void => delegate Action
++ Task task = new Task(Action);
++ Task task = new task(Action(Object), Object); Trong đó:
Action(Object) tương đương delegate: (Object obj) => {}
Object: tham số truyền vào cho obj ở trên
- Lưu ý: Khi các Task chạy trên các thread khác nhau, đến dòng cuối cùng của
hàm mà vẫn có Task khác chưa chạy xong => đóng luôn + Task: có kiểu trả về Syntax: Task task = new Task(myfunc);
*** LINQ - Language Integrated Query
- Nguồn dữ liệu có thể sử dụng LINQ: IEnumrable, IEnumrable (array, list,
stack, queue...), file XML, các csdl SQL được nạp vào chương trình đc thể
hiện thông qua các đối tượng lớp List, Stack...
- Một số phương thức thông dụng: + Select + Where
+ SelectMany: chuyển đổi tất cả các phần tử trong các danh sách, mà danh sách đó là
thuộc tính của 1 phần tử của 1 danh sách các đối tượng => thành một danh sách duy nhất