You are on page 1of 25

C#

* Biến và khai báo


- Khai báo: <datatype> <veriable_name>;

* Hiển thị đầu ra:


- Console.Write hoặc Console.WriteLine. Sự khác biệt là Console.WriteLine theo sau bởi 1 dấu xuống dòng

VD: Console.WriteLine("Hello World!");


- Note: Để ý dấu ngoặc đơn sau phương thức WriteLine. Dấu ngoặc đơn được dùng để truyền dữ liệu hoặc đối số cho
phương thức.
using System;
class project{
static void Main(){
Console.Write("Hello Coche");
}
}
- Để hiển thị 1 chuỗi đã định dạng:
Console.WriteLine("x = {0}; y = {1}", x, y);
Output: x = 10; y = 20

* Nhận đầu vào từ người dùng:


- Console.ReadLine(): nhận input
- double.Parse is a static method defined in the System namespace.
string input = Console.ReadLine();
double a = double.Parse(input);
* Để ý các dấu ngoặc đơn trống trong phương thức ReadLine. Điều này có nghĩa là phương thức không nhận bất kỳ
đối số nào.
- Phương thức Console.ReadLine() trả về giá trị là một chuổi, để trả về các kiểu dữ liệu khác:
+ Để chuyển đổi, ta sử dụng các phương thức Convert.ToXXX, trong đó XXX là tên .NET của kiểu dữ liệu cần chuyển
đổi.
Ví dụ: Convert.ToDouble và Convert.ToBoolean.
Convert.ToDouble.(Console.ReadLine())
+ Để chuyển đổi sang số nguyên, có ba lựa chọn thay thế dựa trên kích thước bit của số nguyên: Convert.ToInt16,
Convert.ToInt32 và Convert.ToInt64. Kiểu int mặc định trong C# là 32-bit.
- Lưu ý: Nhập giá trị không phải số nguyên sinh ra lỗi

* Comment
- Bắt đầu bằng 2 dấu //
- Comment nhiều dòng /*..........*/

* Ép kiểu:
int a = (int)x

* Từ khóa var
- Biến được khia báo bằng var là biến kiều định ngầm nên ta phải khỏi tạo giá trij5 ngay khai báo
var num = 42 //true

var num
num = 42 //false
- const: hằng số -> không thể thay đổi trong quá trình thực thi, hằng số phải được khởi tạo khi khai báo
Vd: const pi = 3.14
pi = 8 //error

* Dạng tiền tố và hậu tố


++x; //prefix
x++; //postfix
Tiền tố thực hiện tăng giá trị, rồi tiếp tục đánh giá biểu thức.
int x = 3;
int y = ++x;
Console.WriteLine(x+" "+y);
//4 4
Hậu tố đánh giá biểu thức và sau đó thực hiện tăng giá trị.
int x = 3;
int y = x++;
Console.WriteLine(x+" "+y);
//4 3

* Câu lệnh if:


- Khi chỉ có một dòng lệnh trong khối if, dấu ngoặc nhọn có thể được bỏ qua.
if (condition)
{
// Execute this code when condition is true
}

* Mệnh đề else:
- Chúng ta có thể chỉ định một mệnh đề else tùy chọn thực thi một khối lệnh khi điều kiện trong câu lệnh if được
đánh giá là false.
if (condition)
{
//statements
}
else
{
//statements
}

* Câu lệnh switch:


switch(value){
case value1:
//statement
break;
case value_n:
...
default:
...
}
- Câu lệnh switch cung cấp cách ngắn gọn để kiểm tra xem một biến có bằng một trong các giá trị trong danh sách các
giá trị hay không.
- Mỗi giá trị là một case (trường hợp) và biến được kiểm tra sẽ được so sánh với các giá trị này một cách trực tiếp.
- Trong một câu lệnh switch, trường hợp mặc định tùy chọn được thực thi khi không có trường hợp nào khớp.
- Ở mỗi case nếu không có break, sẽ dẫn đến hành vi fallthrough (fail to happen)

* Câu lệnh break:


- Khi xuất hiện break, nó sẽ thoát ra khỏi hàm thực thi

* Câu lệnh continue:


- Bỏ tất cả câu lệnh phía dưới continue để thực hiện bước lặp tiếp theo

* Vòng lặp for:


for ( init; condition; increment ) {
statement(s);
}
- Có thể bỏ init và increment, nhưng đảm bảo vẫn có ;
for( ; condition; increment)
for( ; condition; )
- for (; ;) {} là một vòng lặp vô hạn.

* Vòng lặp do-while:


do {
//statement
}while(condition);
- Đảm bảo vòng lặp thực hiện ít nhất 1 lần

* Toán tử điều kiện


- Toán tử ?: <condition> ? <true> : false;
if(age >= 18)
msg = "Welcome";
else
msg = "Sorry";
-->msg = (age >= 18) ? "Welcome" : "Sorry";

* Giới thiệu phương thức


- Phương thức là một nhóm các câu lệnh thực hiện một nhiệm vụ cụ thể. Ngoài phương thức tích hợp sẵn trong C#,
bạn cũng có thể tạo phương thức cho riêng mình.
- Mọi chương trình C# hợp lệ có ít nhất một phương thức Main

* Khai báo phương thức


- Để sử dụng phương thức bạn cần khai báo và gọi nó
- Phần khai báo phương thức bao gồm:
kiểu trả về
tên phương thức
danh sách các tham số tùy chọn.
- Syntax:
<return type> name(type1 par1, type2 par2, … , typeN parN)
{
List of statements
}
- void là một kiểu dữ liệu cơ bản được dùng để định nghĩa một trạng thái không có giá trị.

* Gọi phương thức


- Tham số tùy chọn, tức là phương thức không có tham số
- Để thực thi một phương thức, bạn chỉ cần gọi phương thức bằng cách sử dụng tên và bất kỳ đối số cần thiết nào
trong một câu lệnh.

* Tham số phương thức


- Khai báo phương thức có thể xác định một danh sách các tham số để thực hiện các tác vụ cụ thể.
void Print(int x)
{
Console.WriteLine(x);
}
- Tham số hoạt động trong phương thức tương tự như các biến cục bộ khác. Chúng được tạo ra khi được đưa vào
phương thức và bị hủy khi thoát khỏi phương thức.
- Nhiều tham số: có thể truyền nhiều tham số cho một phương thức bằng cách phân tách chúng bằng dấu phẩy trong
phần khai báo.
int Sum(int x, int y)
{
return x+y;
}
- Phương thức trả về giá trị bằng cách sử dụng câu lệnh return.

* Optional argument (tham số mặc định)


static int Pow(int x, int y=2)
- Phương thức Pow gán giá trị mặc định là 2 cho tham số y. Nếu chúng ta gọi phương thức mà không truyền giá trị
cho tham số y, giá trị mặc định sẽ được sử dụng
- Chỉ cần nhớ rằng, bạn phải có các tham số có giá trị mặc định ở cuối danh sách tham số khi định nghĩa phương
thức.

* Named argument ( tên tham số ): Named Argument sử dụng tên của tham số, tiếp theo là dấu hai chấm và giá trị.
static int Area(int h, int w)
int res = Area(w: 5, h: 8);

* Truyền đối số
- Có ba cách truyền đối số vào phương thức khi phương thức được gọi là bằng giá trị, tham chiếu và giá trị trả về.
- Cách truyền bằng giá trị sẽ sao chép giá trị của đối số vào tham số hình thức của phương thức. Ở đây, ta có thể thay
đổi tham số trong phương thức mà không ảnh hưởng đến đối số.

* Truyền bằng tham chiếu


- Truyền bằng tham chiếu sẽ sao chép địa chỉ bộ nhớ của đối số vào tham số hình thức. Bên trong phương thức, địa
chỉ được sử dụng để truy cập đối số thực tế được sử dụng trong lệnh gọi. Điều này có nghĩa là các thay đổi được
thực hiện trên tham số sẽ ảnh hưởng đến đối số.
- Từ khóa ref truyền địa chỉ bộ nhớ cho tham số phương thức, cho phép phương thức hoạt động trên biến thực tế.
static void Sqr(ref int x)
---Sqr(ref x);

* Truyền bằng giá trị trả về


- Tham số đầu ra tương tự như tham số tham chiếu, ngoại trừ việc chúng chuyển dữ liệu ra khỏi phương thức thay vì
nhận dữ liệu vào. Chúng được khai báo bằng từ khóa out.
- Biến được cung cấp cho tham số đầu ra không cần khởi tạo vì giá trị đó sẽ không được sử dụng. Tham số đầu ra rất
hữu ích khi bạn cần trả về nhiều giá trị từ một phương thức.
- static void GetValues(out int x, out int y)
{
x = 5;
y = 42;
}
--GetValues(out a, out b);

* Nạp chồng phương thức (cùng tên phuong2 thức, khác tham số)
- Nạp chồng phương thức là khi có nhiều phương thức cùng tên nhưng chứa các tham số khác nhau.
- Nạp chồng phương thức cho phép sử dụng cùng tên phương thức cho các kiểu dữ liệu khác nhau, chẳng hạn như
double.
* Lưu ý:
Bạn không thể nạp chồng các khai báo phương thức chỉ khác nhau ở kiểu trả về.
Khai báo sau sẽ gây lỗi:
int PrintName(int a) { }
float PrintName(int b) { }
double PrintName(int c) { }

* Đệ quy:
- Phương thức đệ quy là một phương thức gọi chính nó.
- Điều kiện thoát để ngăn đệ quy vô hạn.
string.Compare(sortedStr1, sortedStr2)
string sortedStr2 = new string(str2_ch); // Converting char array str2_ch to string
System.Array.Sort(str2_ch); // Sorting char array str2_2
char[] str2_ch = str2.ToCharArray(); // Converting string to char array to sort
str1 = str1.ToLower(); // Converting all characters of string 1 to lowercase

* Lớp
- Trong lập trình hướng đối tượng, lớp là một kiểu dữ liệu định nghĩa một tập hợp các biến và phương thức cho một
đối tượng được khai báo.
----Ví dụ, nếu bạn tạo một chương trình quản lý tài khoản ngân hàng, lớp BankAccount có thể được sử dụng để khai
báo một đối tượng chứa tất cả các thuộc tính và phương thức cần thiết để quản lý một tài khoản ngân hàng cá nhân,
chẳng hạn như biến balance và các phương thức Deposit và Withdrawal.
- Lớp giống như một bản thiết kế. Nó xác định dữ liệu và hành vi cho một loại đối tượng cụ thể trong chương trình.
Định nghĩa lớp bắt đầu bằng từ khóa class, theo sau là tên lớp. Thân lớp chứa dữ liệu và các hành động được bao
quanh bởi dấu ngoặc nhọn.
class BankAccount
{
//variables, methods, etc.
}
- Lớp định nghĩa một kiểu dữ liệu cho các đối tượng, nhưng bản thân nó không phải là một đối tượng. Đối tượng là
một thực thể cụ thể dựa trên lớp và đôi khi được gọi là một thực thể của lớp.
- Lớp được sử dụng để khai báo nhiều đối tượng

* Đối tượng
- Đối tượng (object) được gọi là một thực thể của lớp
- Thuật ngữ kiểu (type) được sử dụng để đề cập đến tên lớp: Chúng ta tạo một đối tượng có một kiểu nhất định.
- Các đặc điểm của đối tượng được gọi là thuộc tính, quyết định trạng thái hiện tại của một đối tượng (Attribute)
- Việc tạo ra một đối tượng được gọi là khởi tạo (instantiation)
Ví dụ, một người (một đối tượng của lớp Person) có thể là nam, 30 tuổi và có tên là Antonio.

* Kiểu giá trị


- Trong C#, có hai cách để lưu trữ dữ liệu: theo tham chiếu và theo giá trị.
- Các kiểu dữ liệu được tích hợp sẵn, chẳng hạn như int và double, được sử dụng để khai báo các biến kiểu giá trị. Giá
trị của chúng được lưu trữ trong bộ nhớ tại vị trí được gọi là stack (ngăn xếp).
- Khi biến thuộc các kiểu sau, mặc định nó ứng xử là kiểu giá trị:
+ Các kiểu số nguyên như int, byte, long ...
+ Các kiểu số thực như float, double, decimal
+ Kiểu bool, kiểu char
+ Kiểu cấu trúc struct
+ Kiểu liệt kê enum
+ Kiểu Tuple

* Kiểu tham chiếu


- Kiểu tham chiếu được sử dụng để lưu trữ đối tượng. Ví dụ, khi bạn tạo một đối tượng của lớp, nó được lưu trữ
dưới dạng kiểu tham chiếu.
Kiểu tham chiếu được lưu trữ trong một phần của bộ nhớ được gọi là heap.
- Những biển có kiểu sau thì nó là kiểu tham chiếu:
+ Biến kiểu lớp (class), các lớp thư viện hoặc lớp do bạn định nghĩa
+ Biến kiểu delegate
+ Biến kiểu interface, các giao diện từ thư viện hoặc tự định nghĩa
+ Biến kiểu dynamic
+ Biến kiểu object
+ Biến kiểu string
Khi bạn khởi tạo một đối tượng, dữ liệu cho đối tượng đó được lưu trữ trên heap, trong khi địa chỉ bộ nhớ heap của
nó được lưu trữ trên stack.
- Stack được sử dụng cho phân bổ bộ nhớ tĩnh, bao gồm tất cả các kIểu giá trị, chẳng hạn như x.
- Heap được sử dụng cho phân bổ bộ nhớ động, bao gồm các đối tượng tùy chỉnh, có thể cần bộ nhớ bổ sung trong
quá trình thực thi của chương trình.

* Lớp
- Bạn có thể thêm một trình truy cập cho các trường và phương thức (còn gọi là thành viên) của lớp. Trình truy cập
(access modifier) là các từ khóa được sử dụng để chỉ định phạm vi truy cập của một thành viên.
- Một thành viên được xác định là public có thể được truy cập từ bên ngoài lớp, miễn là nó ở bất kỳ nơi nào trong
phạm vi đối tượng của lớp. Đó là lý do tại sao phương thức SayHi được khai báo là public, vì ta sẽ gọi nó từ bên ngoài
lớp.
--> Bạn cũng có thể chỉ định các thành viên của lớp là private hoặc protected. Phần này sẽ được thảo luận kỹ hơn sau
trong khóa học. Nếu không có trình truy cập nào được chỉ định, thành viên sẽ là private theo mặc định.
- Toán tử new khởi tạo một đối tượng và trả về một tham chiếu đến vị trí của nó
- Toán tử chấm (.) được sử dụng để truy cập và gọi phương thức của đối tượng.

-------------------------------------------------------
ENCAPSULATION (TÍNH ĐÓNG GÓI)
- Một mặt nghĩa của từ encapsulation (đóng gói) là "bao quanh" một thực thể, không chỉ để kết hợp những thứ bên
trong lại với nhau mà còn để bảo vệ chúng.
- Tính đóng gói được triển khai bằng cách sử dụng trình truy cập. Trình truy cập xác định phạm vi và khả năng truy
cập của một thành viên của lớp.
-> Đóng gói còn được gọi là ẩn thông tin.
)> C# hỗ trợ các từ khóa chỉ định truy cập sau: public, private, protected, internal, protected internal.
- Từ khóa chỉ định truy cập public làm cho thành viên có thể được truy cập từ bên ngoài lớp.
- Từ khóa chỉ định truy cập private làm cho thành viên chỉ có thể được truy cập từ bên trong lớp và được ẩn khỏi bên
ngoài lớp.
* Tóm lại, những lợi ích của tính đóng gói là:
+ Kiểm soát cách truy cập hoặc thay đổi dữ liệu.
+ Code linh hoạt và dễ thay đổi theo yêu cầu mới.
+ Thay đổi một phần code mà không ảnh hưởng đến các phần khác của chương trình.
---------------------------------
ARRAY (LÝ THUYẾT MẢNG)
- C# cung cấp nhiều lớp tích hợp sẵn để lưu trữ và xử lý dữ liệu.
- Một ví dụ cụ thể là lớp Array (Mảng).
- Mảng là một cấu trúc dữ liệu được sử dụng để lưu trữ một bộ sưu tập dữ liệu. Bạn có thể nghĩ đến nó như một bộ
sưu tập các biến có cùng kiểu dữ liệu.
- Declaration: <type_value>[ ] myArray;
- Vì mảng là đối tượng, chúng ta cần khởi tạo chúng với từ khóa new:
int[ ] myArray = new int[5];
>> Lưu ý các dấu ngoặc vuông được sử dụng để xác định số lượng phần tử mà mảng cần lưu trữ.
- Sau khi tạo mảng, bạn có thể gán giá trị cho các phần tử riêng lẻ bằng cách sử dụng chỉ số:
int[ ] myArray = new int[5];
myArray[0] = 23;
- Chỉ số mảng trong C# bắt đầu từ 0, có nghĩa là phần tử đầu tiên có chỉ số 0, phần tử thứ hai có chỉ số 1 và cứ tiếp
tục như vậy.
- Chúng ta có thể cung cấp các giá trị khởi tạo cho mảng khi nó được khai báo bằng cách sử dụng dấu ngoặc nhọn
string[ ] names = new string[3] {"John", "Mary", "Jessica"};
double[ ] prices = new double[4] {3.6, 9.8, 6.4, 5.9};
- Trong C#, để lấy ra kích thước của một mảng chúng ta sử dụng method Length():
myArray.Length
- Vòng lặp foreach cung cấp cách ngắn gọn hơn và dễ dàng hơn để truy cập các phần tử mảng.
foreach (int k in a) {
Console.WriteLine(k);
}
>> Kiểu dữ liệu của biến trong vòng lặp foreach phải khớp với kiểu dữ liệu của các phần tử tmảng.
>> Thường thì từ khóa var được sử dụng làm kiểu của biến, như trong: foreach (var k in a). Trình biên dịch sẽ xác định
kiểu thích hợp cho var.

* Mảng đa chiều
- Một mảng có thể có nhiều chiều. Mảng đa chiều được khai báo như sau:
type[, , … ,] arrayName = new type[size1, size2, …, sizeN];
Ví dụ định nghĩa một mảng hai chiều gồm 3 hàng và 4 cột chứa các số nguyên:
int[ , ] x = new int[3,4];
- Chỉ số mảng bắt đầu từ 0.
int[ , ] someNums = { {2, 3}, {5, 6}, {4, 6} };
Để truy cập một phần tử của mảng, ta phải cung cấp cả hai chỉ số. Ví dụ someNums[2, 0] sẽ trả về giá trị 4, vì nó truy
cập cột đầu tiên của hàng thứ ba.
- The .Length property returns the number of elements in an array, whether it be one dimensional or
multidimensional. That is a 2x6 array will have length of 12.
- The .GetLength(0) method returns number of elements in the row direction in a multidimensional array. For a 2x6
array that is 2.
- The .GetLength(1) method returns number of elements in the column direction in a multidimensional array. For a
2x6 array that is 6.

* Mảng răng cưa


- Mảng răng cưa là một loại mảng trong đó các phần tử của nó là các mảng con. Vì vậy, nó chỉ đơn giản là một mảng
các mảng.
- Dưới đây là chương trình khai báo một mảng một chiều gồm ba phần tử, mỗi phần tử là một mảng số nguyên một
chiều:
int[ ][ ] jaggedArr = new int[3][ ];
- Mỗi chiều là một mảng, vì vậy bạn cũng có thể khởi tạo mảng khi khai báo như sau:
int[ ][ ] jaggedArr = new int[ ][ ]
{
new int[ ] {1,8,2,7,9},
new int[ ] {2,4,6},
new int[ ] {33,42}
}; //gọi jaggeArr[0][0]
- Mảng răng cưa là một mảng các mảng, vì vậy int[][] là một mảng con của int[], mỗi mảng có thể có độ dài khác nhau
và chiếm một không gian riêng trong bộ nhớ.
- Mảng đa chiều (int[,]) được lưu trữ trong một khối bộ nhớ duy nhất, thường được sử dụng để lưu trữ dữ liệu theo
hình dạng ma trận. Số lượng cột của mảng đa chiều là cố định cho mỗi hàng.
//System.Console.Write("{0} ",col); cách in

* Thuộc tính & phương thức mảng


- Thuộc tính mảng:
+ Lớp Array trong C# cung cấp các thuộc tính và phương thức khác nhau để làm việc với mảng.
+ Các thuộc tính Length và Rank trả về số lượng phần tử và số chiều của mảng. Bạn có thể truy cập chúng bằng cú
pháp dấu chấm giống như bất kỳ thành viên nào của lớp:
Console.WriteLine(arr.Length);
Console.WriteLine(arr.Rank);
- Phương thức mảng:
C# cung cấp nhiều phương thức tích hợp sẵn cho mảng.
Max() trả về giá trị lớn nhất.
Min() trả về giá trị nhỏ nhất.
Sum() trả về tổng của tất cả các phần tử

* Làm việc với Chuỗi:


- Chuỗi: Chuỗi thường được coi là một mảng ký tự. Trên thực tế, chuỗi trong C# là đối tượng.
- Khi khai báo một biến string, bạn đang khởi tạo một đối tượng có kiểu String.
- Đối tượng String hỗ trợ nhiều thuộc tính và phương thức hữu ích như:
Length trả về độ dài của chuỗi.
IndexOf(value) trả về chỉ số đầu tiên mà một giá trị xuất hiện trong chuỗi.
Insert(index, value) chèn giá trị vào chuỗi tại một chỉ số xác định.
Remove(index) loại bỏ tất cả các ký tự trong chuỗi từ vị trí chỉ số xác định đến hết chuỗi.
Replace(oldValue, newValue) thay thế một giá trị cụ thể trong chuỗi bằng một giá trị mới.
Substring(index, length) trả về một chuỗi con có độ dài xác định, bắt đầu tại một chỉ số xác định. Nếu không chỉ định
độ dài thì thao tác tiếp tục đến cuối chuỗi.
Contains(value) trả về true nếu chuỗi chứa giá trị được chỉ định.
- Bạn cũng có thể truy cập các ký tự của chuỗi bằng cách sử dụng chỉ số, giống như cách truy cập các phần tử của
mảng
- Làm việc với chuỗi:

* Hàm hủy (Destructor)


- Giống như cách hàm khởi tạo được tự động gọi khi một lớp được khởi tạo, hàm hủy (destructor) được tự động gọi
khi một đối tượng bị hủy hoặc xóa.
- Hàm hủy có các thuộc tính sau:
+ Một lớp chỉ có thể có một hàm hủy.
+ Hàm hủy không thể gọi. Chúng được tự động gọi.
+ Hàm hủy không nhận trình truy cập hoặc có tham số.
+ Tên của hàm hủy giống chính xác tên của lớp nhưng thêm tiền tố dấu ngã (~).
+ Cho tới hết chương trình thì hàm hủy mới được gọi
class Dog
{
~Dog()
{
// code statements
}
}
>> Hàm hủy có thể giúp giải phóng tài nguyên trước khi thoát khỏi chương trình. Đó có thể là đóng file, giải phóng bộ
nhớ, v.v.

* Thành viên static


- Trong bài học này, chúng ta sẽ thảo luận về từ khóa static.
Chúng ta đã từng nhìn thấy từ khóa này trong khai báo phương thức Main:
static void Main(string[] args)
- Các thành viên của lớp (biến, thuộc tính, phương thức) cũng có thể được khai báo là static. Điều này làm cho những
thành viên đó thuộc về lớp mà chúng được khai báo, thay vì thuộc về các đối tượng riêng lẻ. Các đối tượng của lớp
sẽ chia sẻ cùng một bản sao của các thành viên static này, thay vì có một bản sao riêng biệt cho mỗi đối tượng.
Ví dụ:
class Cat {
public static int count=0;
public Cat() {
count++;
}
}
Trong trường hợp này, chúng ta khai báo một biến thành viên public là count, được xác định là static. Hàm khởi tạo
của lớp tăng biến count lên 1 đơn vị.
>> Bất kể có bao nhiêu đối tượng Cat được khởi tạo, luôn chỉ có một bản sao của biến count được lưu trữ trong lớp
Cat vì nó đã được khai báo là static. (chỉ có 1 biến count cho toàn bộ lớp Cat)
- Như bạn thấy, chúng ta có thể truy cập biến static bằng cách sử dụng tên lớp: Cat.count.
Biến count được chia sẻ giữa tất cả các đối tượng Cat. Đối với lớp này, mỗi lần tạo một đối tượng, giá trị static sẽ
được tăng lên. Chương trình trên minh họa điều này khi giá trị 2 được hiển thị sau khi tạo hai đối tượng của lớp đó.
* Phương thức static
- Khái niệm tương tự có thể áp dụng cho phương thức static.
- Phương thức static chỉ có thể truy cập các thành viên static
- Phương thức Main là static vì nó là điểm bắt đầu của bất kỳ chương trình C# nào. Do đó, bất kỳ phương thức nào
được gọi trực tiếp từ Main đều phải là static.
- Thành viên constant (hằng số) là static theo mặc định.
- Console.Write(MathClass.ONE);
- Như bạn thấy, chúng ta truy cập vào thuộc tính ONE bằng tên lớp giống như cách truy cập thành viên static. Điều
này bởi vì tất cả các thành viên const đều là static theo mặc định.

* Static constructor
- Hàm khởi tạo có thể được khai báo là static để khởi tạo các thành viên static của lớp.
- Static constructor được tự động gọi một lần khi ta truy cập một thành viên static của lớp.
class SomeClass {
public static int X { get; set; }
public static int Y { get; set; }

static SomeClass() {
X = 10;
Y = 20;
}
}
>> Hàm khởi tạo sẽ được gọi một lần khi ta truy cập SomeClass.X hoặc SomeClass.Y.

* Lớp static
- Một lớp hoàn chỉnh có thể được khai báo là static.
- Lớp static chỉ có thể chứa các thành viên static.
- Bạn không thể tạo được các đối tượng từ lớp static bởi vì trong chương trình chỉ có một thực thể của lớp static tồn
tại.
- Các lớp static giúp kết hợp các thuộc tính và phương thức logic. Một ví dụ cụ thể là lớp Math.
- Nó chứa các thuộc tính và phương thức hữu ích để thực hiện những phép toán toán học.
- Bạn có thể truy cập tất cả các thành viên của lớp Math bằng tên lớp mà không cần khai báo một đối tượng.
- Một lớp static chứa: chỉ có các thành viên static
- Ngoài ra C# cũng có cung cấp một số phương thức và thuộc tính static hữu ích như:
Math.PI biểu diễn hằng số PI.
Math.E đại diện cho cơ số logarithm tự nhiên e.
Math.Max() trả về giá trị lớn nhất trong hai đối số.
Math.Min() trả về giá trị nhỏ nhất trong hai đối số.
Math.Abs() trả về giá trị tuyệt đối của một số.
Math.Sin() trả về sin của một góc.
Math.Cos() trả về cos của một góc.
Math.Pow() tính giá trị của một số bất kỳ được tăng lên một số mũ cụ thể.
Math.Round() làm tròn số thập phân đến giá trị nguyên gần nhất.
Math.Sqrt() trả về căn bậc hai của một số.
- Lớp Array bao gồm một số phương thức tĩnh hỗ trợ xử lý mảng:
Array.Reverse(arr);
Array.Sort(arr);
- Lớp String:
String.Concat(s1, s2); // combines the two strings
String.Equals(s1, s2); // returns false
- Lớp DateTime
+ Cấu trúc DateTime cho phép bạn thực hiện các thao tác liên quan đến ngày giờ.
DateTime.Now
DateTime.Today
DateTime.DaysInMonth(2016, 2)
- Lớp Console cũng là một ví dụ về lớp static. Chúng ta sử dụng phương thức static WriteLine() để in kết quả ra màn
hình hoặc phương thức static ReadLine() để lấy đầu vào từ người dùng.
- Lớp Convert được sử dụng để chuyển đổi kiểu dữ liệu cũng là một lớp static.

* Từ khóa this
- Từ khóa this được sử dụng bên trong lớp và tham chiếu đến thực thể hiện tại của lớp, có nghĩa là nó tham chiếu
đến đối tượng hiện tại.
- Từ khóa this thường được sử dụng để phân biệt các thành viên của lớp với dữ liệu khác, như các tham số cục bộ
hoặc hình thức của phương thức, như trong ví dụ sau:
class Person {
private string name;
public Person(string name) {
this.name = name;
}
}
- Ở đây, this.name đại diện cho thành viên của lớp, trong khi name đại diện cho tham số của hàm khởi tạo.
- Một ứng dụng phổ biến khác của từ khóa this là truyền thực thể hiện tại đến một phương thức dưới dạng tham số:
ShowPersonInfo(this);

* Từ khóa chỉ định truy cập readonly


- readonly là từ khóa được sử dụng để ngăn chặn một thành viên của lớp bị sửa đổi sau khi khởi tạo. Trường được
khai báo là readonly chỉ có thể được sửa đổi khi bạn khai báo hoặc từ trong hàm khởi tạo.
- Nếu chúng ta cố gắng sửa đổi trường name ở bất kỳ đâu khác, chúng ta sẽ nhận được một lỗi.
- Có ba điểm khác biệt chính giữa các trường readonly và const.
>> Thứ nhất, trường constant phải được khởi tạo khi khai báo, trong khi trường readonly có thể được khai báo mà
không cần khởi tạo, ví dụ:
readonly string name; // OK
const double PI; // Error
>> Thứ hai, giá trị của trường readonly có thể được thay đổi trong hàm khởi tạo, nhưng giá trị constant không thể
thay đổi.
>> Thứ ba, trường readonly có thể được gán giá trị là kết quả của một phép tính, nhưng constant thì không thể, ví
dụ:
readonly double a = Math.Sin(60); // OK
const double b = Math.Sin(60); // Error!
- Sử dụng từ khóa this với các thành viên của lớp để gán giá trị.
this.num = num;

* Indexer
Indexer cho phép các đối tượng của lớp được lập chỉ mục bằng cách sử dụng các dấu ngoặc vuông giống như mảng.
Như đã thảo luận trước đó, biến string thực ra là một đối tượng của lớp String. Ngoài ra, lớp String thực ra là một
mảng ký tự (đối tượng Char). Theo nghĩa đó, lớp string triển khai một indexer để ta có thể truy cập vào bất kỳ ký tự
nào (đối tượng Char) bằng chỉ số của nó:
string str = "Hello World";
char x = str[4];
>> Mảng sử dụng chỉ số nguyên, nhưng indexer có thể sử dụng chỉ số có kiểu dữ liệu bất kỳ, chẳng hạn như chuỗi, ký
tự, v.v.
- Cú pháp khai báo indexer có chút giống với cú pháp khai báo thuộc tính. Khác biệt duy nhắt là phương thức truy cập
indexer yêu cầu một chỉ số.
- Giống như thuộc tính, bạn sử dụng phương thức truy cập get và set để xác định một indexer. Tuy nhiên, trong khi
thuộc tính trả về hoặc thiết lập một thành viên dữ liệu cụ thể, indexer trả về hoặc thiết lập một giá trị cụ thể từ thực
thể của đối tượng.
- Indexer được khai báo bằng từ khóa this.
class Clients {
private string[] names = new string[10];

public string this[int index] {


get {
return names[index];
}
set {
names[index] = value;
}
}
- Như bạn thấy, phần khai báo indexer bao gồm từ khóa this và một chỉ số, chỉ số được sử dụng để lấy và thiết lập giá
trị thích hợp.
- Bây giờ, khi chúng ta khai báo một đối tượng của lớp Clients, chúng ta sử dụng chỉ số để tham chiếu đến các đối
tượng cụ thể như phần tử mảng:
>> Indexer thường được sử dụng khi lớp đại diện cho một danh sách, bộ sưu tập hoặc mảng đối tượng.

* Nạp chổng toán tử


- Hầu hết các toán tử trong C# có thể được nạp chồng, có nghĩa là người dùng có thể định nghĩa lại cách thức hoạt
động của các toán tử này.
+ Ví dụ, bạn có thể định nghĩa lại hành động của toán tử cộng (+) trong một lớp tùy chỉnh.
Xét lớp Box có các thuộc tính Height và Width:
class Box {
public int Height {get; set;}
public int Width {get; set;}
public Box(int h, int w) {
Height = h;
Width = w;
}
}
static void Main(string[] args) {
Box b1 = new Box(14, 3);
Box b2 = new Box(5, 7);
}
Chúng ta muốn kết hợp hai đối tượng Box, kết quả sẽ là một Box mới lớn hơn.
Vì vậy, cơ bản, chúng ta muốn chương trình sau đây hoạt động:
Box b3 = b1 + b2
>> Thao tác này hợp lệ nhờ có nạp chồng toán tử.
- Các toán tử được nạp chồng là các phương thức đặc biệt có tên là operator, theo sau bởi ký hiệu của toán tử tương
ứng
- Tương tự như bất kỳ phương thức nào khác, toán tử được nạp chồng có một kiểu trả về và một danh sách tham số.
- Ví dụ, đối với lớp Box ở trên, chúng ta nạp chồng toán tử +:
public static Box operator+ (Box a, Box b) {
int h = a.Height + b.Height;
int w = a.Width + b.Width;
Box res = new Box(h, w);
return res;
}
- Phương thức trên định nghĩa một toán tử nạp chồng + với hai tham số đối tượng Box và trả về một đối tượng Box
mới với các thuộc tính Height và Width bằng tổng các thuộc tính tương ứng của hai tham số.
>> Ngoài ra, toán tử được nạp chồng phải là static.
>> Tất cả các toán tử số học và so sánh có thể được nạp chồng. Ví dụ, bạn có thể định nghĩa các toán tử lớn hơn và
nhỏ hơn cho lớp Hộp để so sánh các đối tượng Hộp và trả về một kết quả boolean. Nhớ rằng khi nạp chồng toán tử
lớn hơn, toán tử nhỏ hơn cũng nên được định nghĩa.

* Tính kế thừa
- Kế thừa cho phép chúng ta xác định một lớp dựa trên một lớp khác. Điều này giúp việc tạo và duy trì các ứng dụng
trở nên dễ dàng hơn.
- Lớp có thuộc tính được kế thừa từ một lớp khác được gọi là lớp Cơ sở. Lớp kế thừa các thuộc tính được gọi là lớp
Dẫn xuất.
Ví dụ, lớp cơ sở Animal có thể được sử dụng để tạo ra các lớp Cat và Dog.
Lớp dẫn xuất kế thừa tất cả các tính năng từ lớp cơ sở và có thể có các tính năng bổ sung riêng của nó.
Base clase (lớp cha)
Derived class (lớp con)
class Animal //lớp cha
class Dog : Animal //lớp con - kế thừa
- Để ý cú pháp của lớp dẫn xuất. Dấu hai chấm và tên lớp cơ sở đứng sau tên lớp dẫn xuất.
- Tất cả các thành viên public của Animal trở thành thành viên public của Dog. Đó là lý do tại sao chúng ta có thể truy
cập thành viên Legs trong hàm khởi tạo của Dog.
class Animal {
public int Legs {get; set;}
public int Age {get; set;}
}
-
class Dog : Animal {
public Dog() {
Legs = 4;
}
public void Bark() {
Console.Write("Woof");
}
}
>> Kế thừa cho phép lớp dẫn xuất sử dụng lại code trong lớp cơ sở mà không cần phải viết lại. Và lớp dẫn xuất có thể
được tùy chỉnh bằng cách thêm nhiều thành viên hơn. Bằng cách này, lớp dẫn xuất có thể mở rộng tính năng của lớp
cơ sở.
>> C# không hỗ trợ đa kế thừa, vì vậy bạn không thể kế thừa từ nhiều lớp.
>> Tuy nhiên, bạn có thể sử dụng interface để triển khai đa kế thừa. Bạn sẽ tìm hiểu thêm về interface trong các bài
học sau.
* Thành viên protected
- Trong các bài học trước, chúng ta chủ yếu làm việc với các trình truy cập public và private.
- Các thành viên public có thể truy cập từ bất kỳ đâu bên ngoài lớp, trong khi các thành viên private chỉ có thể được
truy cập từ bên trong lớp của chúng.
- Trình truy cập protected rất giống private nhưng có một điểm khác biệt là nó có thể được truy cập trong lớp dẫn
xuất. Vì vậy, thành viên protected chỉ có thể được truy cập từ các lớp dẫn xuất.

* Thành viên sealed


- Một lớp có thể ngăn các lớp khác kế thừa từ nó hoặc bất kỳ thành viên nào của nó bằng cách sử dụng từ khóa
sealed.
>> Từ khóa sealed cung cấp một mức độ bảo vệ cho lớp để các lớp khác không thể kế thừa từ nó.
class A {
}
sealed class B : A {
}

* Hàm tạo lớp dẫn xuất & Hàm hủy


- Hàm khởi tạo được gọi khi các đối tượng của một lớp được tạo ra. Trong kế thừa, hàm khởi tạo và hàm hủy của lớp
cơ sở không được kế thừa bởi các lớp dẫn xuất, vì vậy bạn nên định nghĩa các hàm khởi tạo và hàm hủy riêng cho lớp
dẫn xuất.
- Tuy nhiên, hàm khởi tạo và hàm hủy của lớp cơ sở được tự động gọi khi một đối tượng của lớp dẫn xuất được tạo
hoặc xóa.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TEK4VN
{
class Program
{
class Animal {
public Animal() {
Console.WriteLine("Animal created");
}
~Animal() {
Console.WriteLine("Animal deleted");
}
}
class Dog: Animal {
public Dog() {
Console.WriteLine("Dog created");
}
~Dog() {
Console.WriteLine("Dog deleted");
}
}
static void Main(string[] args)
{
Dog d = new Dog();
}
}
}
// Result:
Animal created
Dog created
Dog deleted
Animal deleted
>> Lưu ý rằng hàm khởi tạo của lớp cơ sở được gọi trước và hàm khởi tạo của lớp dẫn xuất được gọi tiếp theo.
>> Khi đối tượng bị hủy, hàm hủy của lớp dẫn xuất được gọi trước và sau đó là hàm hủy của lớp cơ sở được gọi.
>> Bạn có thể hiểu nó như sau: Lớp dẫn xuất cần có lớp cơ sở của nó để hoạt động, đó là lý do tại sao hàm khởi tạo
của lớp cơ sở được gọi trước.

* Tính đa hình
- Khái niệm đa hình:
+ Đa hình có nghĩa là "có nhiều hình thái".
+ Đa hình thường xảy ra khi có một hệ thống các lớp được phân cấp và chúng có quan hệ kế thừa từ một lớp cơ sở
chung.
+ Đa hình có nghĩa là khi ta gọi một phương thức của một đối tượng thì phương thức đó có thể được triển khai khác
nhau tùy thuộc vào loại đối tượng mà chúng ta gọi phương thức đó.
>> Nói một cách đơn giản, đa hình nghĩa là một phương thức có thể có nhiều triển khai khác nhau.
* Tính đa hình trong lập trình hướng đối tượng
- Giả sử có một chương trình cho phép người dùng vẽ các hình học khác nhau. Mỗi hình học có cách vẽ khác nhau và
bạn không biết người dùng sẽ chọn hình gì.
- Ở đây, đa hình có thể được tận dụng để gọi phương thức Draw tương ứng của bất kỳ lớp dẫn xuất nào bằng cách
ghi đè phương thức cùng tên trong lớp cơ sở. Các phương thức này phải được khai báo bằng từ khóa virtual trong
lớp cơ sở.
class Shape {
public virtual void Draw() {
Console.Write("Base Draw");
}
}
- Từ khóa virtual cho phép phương thức được ghi đè trong các lớp dẫn xuất. (ghi ở lớp cha)
>> Các phương thức virtual cho phép bạn làm việc với nhóm các đối tượng liên quan theo một cách đồng nhất.
* Ví dụ tính đa hình trong C#
- Bây giờ, chúng ta có thể dẫn xuất các lớp Shape khác nhau và định nghĩa phương thức Draw của riêng chúng bằng
từ khóa override:
class Circle : Shape {
public override void Draw() {
// draw a circle...
Console.WriteLine("Circle Draw");
}
}
class Rectangle : Shape {
public override void Draw() {
// draw a rectangle...
Console.WriteLine("Rect Draw");
}
}
* Tại sao lại cần tính đa hình?
- Tóm lại, đa hình là một tính chất cho phép gọi cùng một phương thức cho các đối tượng khác nhau và tạo ra các kết
quả khác nhau dựa trên loại đối tượng. Tính đa hình được thực hiện thông qua các phương thức virtual trong lớp cơ
sở.
- Để triển khai điều này, chúng ta tạo ra các đối tượng kiểu cơ sở, nhưng khởi tạo chúng dưới dạng kiểu dẫn xuất:

Shape c = new Circle();


Shape là lớp cơ sở. Circle là lớp dẫn xuất.

- Vậy tại sao cần sử dụng tính đa hình? Tại sao chúng ta không tạo các đối tượng với kiểu của chúng và gọi phương
thức của nó:

Circle c = new Circle();


c.Draw();

- Cách tiếp cận đa hình cho phép ta xử lý mỗi đối tượng theo cùng một cách. Vì tất cả các đối tượng đều thuộc kiểu
Shape, nên code dễ bảo trì và làm việc hơn. Ví dụ, bạn có thể lưu trữ các đối tượng kiểu Shape trong một danh sách
(hoặc mảng) và làm việc với chúng một cách linh hoạt mà không cần biết kiểu dẫn xuất thực tế của mỗi đối tượng.
>> Đa hình có thể hữu ích trong nhiều trường hợp. Ví dụ, chúng ta có thể tạo ra một trò chơi với các kiểu Player khác
nhau và mỗi Player có một hành vi riêng cho phương thức Attack.
>> Trong trường hợp này, Attack sẽ là một phương thức virtual của lớp cơ sở Player và mỗi lớp kế thừa sẽ ghi đè nó.

* Lớp abstract
- Như được mô tả trong ví dụ trước, đa hình được sử dụng khi bạn có nhiều lớp con khác nhau kế thừa từ một lớp
cha và mỗi lớp con có cùng một phương thức với các triển khai khác nhau. Tính đa hình đạt được thông qua các
phương thức virtual được ghi đè trong các lớp dẫn xuất.
- Trong một số tình huống, ta không cần sử dụng phương thức virtual để có một định nghĩa riêng biệt trong lớp cơ
sở.
- Những phương thức này được định nghĩa bằng từ khóa abstract và chỉ định rằng các lớp dẫn xuất phải tự định
nghĩa phương thức đó.
- Bạn không thể tạo đối tượng của một lớp chứa phương thức abstract, vì vậy lớp đó phải là lớp abstract.
- Chúng ta có thể sử dụng phương thức abstract trong lớp Shape như sau:
abstract class Shape {
public abstract void Draw();
}
- Như bạn thấy, phương thức Draw là abstract và không có thân hàm. Bạn thậm chí không cần các dấu ngoặc nhọn;
chỉ cần kết thúc câu lệnh bằng dấu chấm phẩy.
- Lớp Shape bản thân nó phải được khai báo là abstract vì nó chứa một phương thức abstract. Phương thức abstract
chỉ có thể được khai báo trong các lớp abstract.
>> Hãy nhớ rằng phương thức abstract chỉ có thể được khai báo trong lớp abstract. Các thành viên được đánh dấu là
abstract hoặc được thêm vào lớp abstract phải được triển khai bởi các lớp dẫn xuất từ lớp abstract đó. Một lớp
abstract có thể có nhiều thành viên abstract.
- Lớp abstract được thiết kế để làm lớp cơ sở của các lớp khác. Nó cung cấp một mẫu chung cho các lớp dẫn xuất của
nó.
- Bây giờ, khi có lớp abstract, chúng ta có thể dẫn xuất các lớp khác và định nghĩa phương thức Draw() của riêng
chúng:
class Program
{
abstract class Shape {
public abstract void Draw();
}
class Circle : Shape {
public override void Draw() {
Console.WriteLine("Circle Draw");
}
}
class Rectangle : Shape {
public override void Draw() {
Console.WriteLine("Rect Draw");
}
}
static void Main(string[] args)
{
Shape c = new Circle();
c.Draw();
}
}
- Lớp abstract có các tính năng sau:
+ Lớp abstract không thể được khởi tạo.
+ Lớp abstract có thể chứa các phương thức abstract và phương thức truy cập.
+ Lớp non-abstract (lớp cụ thể) được dẫn xuất từ lớp abstract phải bao gồm các triển khai thực của tất cả các phương
thức abstract và phương thức truy cập được kế thừa.
>> Ta không thể sửa đổi một lớp abstract bằng từ khóa sealed vì hai từ khóa này có hành vi trái ngược nhau. Từ khóa
sealed ngăn chặn một lớp được kế thừa và từ khóa abstract yêu cầu một lớp được kế thừa.

* Interface
- Interface là một lớp hoàn toàn trừu tượng, chỉ chứa các thành phần abstract.
Nó được khai báo bằng từ khóa interface:
public interface IShape
{
void Draw();
}
>> Tất cả các thành viên của interface đều mặc định là abstract, vì vậy ta không cần sử dụng từ khóa abstract.
>> Interface có thể có các thành viên public (mặc định), private và protected.
>> Thường ta sử dụng chữ I viết hoa làm chữ cái đầu tiên cho tên interface.
>> Interface có thể chứa thuộc tính, phương thức, vv. nhưng không thể chứa trường (biến).
- Khi lớp triển khai một interface, nó cũng phải triển khai, hoặc định nghĩa, tất cả các phương thức của nó.
- Thuật ngữ triển khai interface được sử dụng (trái ngược với thuật ngữ "kế thừa từ") để mô tả quá trình tạo một lớp
dựa trên interface. Interface chỉ đơn giản mô tả những gì một lớp nên làm. Lớp triển khai interface phải tự định nghĩa
cách thức thực hiện các hành vi được mô tả bởi interface đó.
- Cú pháp triển khai interface giống với cú pháp kế thừa lớp:
- Chú ý, từ khóa override không thực sự cần thiết khi triển khai một interface.
>>
Nhưng tại sao ta cần sử dụng interface thay vì lớp abstract?
Một lớp chỉ có thể kế thừa từ một lớp cơ sở, nhưng nó có thể triển khai nhiều interface!
Do đó, bằng cách sử dụng interface, bạn có thể thêm hành vi từ nhiều nguồn khác nhau vào một lớp.
Để triển khai nhiều interface, ta sử dụng danh sách interface được phân tách bằng dấu phẩy khi tạo lớp: class A:
IShape, IAnimal, vv.
interface A {
}
interface B {
}
class Test : A,B {
}

* Default Implementation
- Default implementation trong interface cho phép viết một triển khai của bất kỳ phương thức nào. Cơ chế này hữu
ích khi cần cung cấp một triển khai cho chức năng chung.
- Giả sử chúng ta cần thêm phương thức mới vào interface đã có sẵn, được triển khai bởi nhiều lớp. Trước khi có
default implementation (trước C# 8), hành vi này sẽ gây ra lỗi nếu các lớp triển khai không triển khai phương thức
này. Trong trường hợp này, các lớp triển khai sẽ phải triển khai phương thức mới đó một cách riêng lẻ. Default
implementation trong interface có thể giúp giải quyết vấn đề này.
>> Các phương thức với default implementation có thể được ghi đè tự do bên trong lớp triển khai interface đó.

* Lớp lồng nhau


- C# hỗ trợ các lớp lồng nhau: một lớp là thành viên của một lớp khác.
- Lớp Motor được lồng trong lớp Car và có thể được sử dụng giống như các thành viên khác của lớp.
- Lớp lồng nhau hoạt động như một thành viên của lớp, vì vậy nó có thể có trình truy cập giống như các thành viên
khác (public, private, protected).
class Car {
string name;
public Car(string nm) {
name = nm;
Motor m = new Motor();
}
public class Motor {
// some code
}
}
>> Giống như ngoài đời thực, các đối tượng có thể chứa các đối tượng khác. Ví dụ, một chiếc xe ô tô với các thuộc
tính riêng của nó (màu sắc, thương hiệu, vv.) chứa một động cơ, đây là một đối tượng riêng biệt với các thuộc tính
riêng (dung tích, mã lực, vv.). Ở đây, lớp Car có thể có một lớp Motor lồng nhau là một trong các thành viên của nó.

* Không gian tên


- Khi bạn tạo một dự án trống, chương trình có cấu trúc như sau:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TEK4VN {
class Program {
static void Main(string[] args) {
}
}
}
- Lưu ý rằng toàn bộ chương trình nằm trong một không gian tên (name space). Vậy không gian tên là gì?
- Không gian tên khai báo phạm vi chứa một tập hợp các đối tượng liên quan. Bạn có thể sử dụng không gian tên để
tổ chức các phần tử câu lệnh. Bạn có thể định nghĩa các không gian tên của riêng bạn và sử dụng chúng trong chương
trình.
- Từ khóa using cho biết chương trình đang sử dụng một không gian tên cụ thể.
Ví dụ, chúng ta sử dụng không gian tên System trong chương trình, trong đó lớp Console được định nghĩa:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TEK4VN
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hi");
}
}
}
- Nếu không có câu lệnh using, chúng ta sẽ phải chỉ định không gian tên ở bất cứ nơi nào nó được sử dụng:

namespace TEK4VN
{
class Program
{
static void Main(string[] args)
{
System.Console.WriteLine("Hi");
}
}
}
.NET Framework sử dụng các không gian tên để tổ chức các lớp. System là một ví dụ về không gian tên của .NET
Framework.
Việc khai báo không gian tên riêng giúp cho việc quản lý và tổ chức các tên lớp và phương thức trong các dự án lập
trình lớn trở nên dễ dàng hơn.

* Struct
Kiểu struct là một kiểu giá trị thường được sử dụng để đóng gói các dữ liệu liên quan thành một nhóm nhỏ, chẳng
hạn như tọa độ của một hình chữ nhật hoặc các đặc điểm của một mặt hàng trong kho. Ví dụ dưới đây minh họa một
khai báo struct đơn giản:

struct Book {
public string title;
public double price;
public string author;
}
- Hầu hết các cú pháp của struct giống như lớp, nhưng nó bị giới hạn hơn so với lớp.
- Không giống như lớp, struct có thể được khởi tạo mà không sử dụng toán tử new.
>> Struct không hỗ trợ kế thừa và không thể chứa phương thức virtual.
- Struct có thể chứa phương thức, thuộc tính, indexer và v.v. Struct không thể chứa hàm khởi tạo mặc định (hàm khởi
tạo không có tham số), nhưng chúng có thể chứa hàm khởi tạo có tham số. Trong trường hợp đó, từ khóa new được
sử dụng để khởi tạo một đối tượng struct, tương tự như đối tượng lớp.
* Struct vs Lớp
- Nói chung, lớp được sử dụng để mô hình hóa hành vi hoặc dữ liệu phức tạp hơn, với mục đích để sửa đổi sau khi
đối tượng lớp được tạo ra. Struct phù hợp với các cấu trúc dữ liệu nhỏ chứa dữ liệu không cần sửa đổi sau khi struct
được tạo. Hãy xem xét sử dụng struct thay vì lớp nếu bạn muốn biểu diễn một tập dữ liệu đơn giản.
>> Tất cả các kiểu dữ liệu C# tiêu chuẩn (int, double, bool, char, vv.) đều là struct.

* Enum (kiều liệt kê)


- Từ khóa enum được sử dụng để khai báo kiểu liệt kê (enumeration): một kiểu dữ liệu bao gồm một tập hợp các
hằng số có tên gọi là danh sách liệt kê.
- Theo mặc định, thành viên đầu tiên của enum có giá trị là 0 và giá trị của mỗi thành viên tiếp theo tăng lên 1. Ví dụ,
trong danh sách liệt kê sau đây, Sun có giá trị 0, Mon có giá trị 1, Tue có giá trị 2 và cứ tiếp tục như vậy:
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
- Trong ví dụ trên, liệt kê sẽ bắt đầu từ giá trị 0 với hằng số Sun, sau đó Mon có giá trị là 1, Tue có giá trị là 4, Wed có
giá trị là 5, và cứ tiếp tục như vậy. Giá trị của mục tiếp theo trong Enum là 1 đơn vị tăng so với giá trị trước đó.
- Lưu ý rằng các giá trị được phân tách bằng dấu phẩy.
- Bạn có thể tham chiếu đến các giá trị trong Enum bằng cú pháp dấu chấm.
- Để gán giá trị Enum cho biến int, bạn phải chỉ định kiểu trong dấu ngoặc đơn.
int x = (int)Days.Tue;
Console.WriteLine(x); //2
Về cơ bản, Enums định nghĩa các biến đại diện cho các thành viên của một tập hợp cố định.
Một số ví dụ sử dụng Enum bao gồm các tên tháng, các ngày trong tuần, các lá bài trong bộ bài, v.v.
Liệt kê thường được sử dụng với câu lệnh switch.
enum TrafficLights { Green, Red, Yellow };
static void Main(string[] args)
{
TrafficLights x = TrafficLights.Red;
switch (x) {
case TrafficLights.Green:
Console.WriteLine("Go!");
break;
case TrafficLights.Red:
Console.WriteLine("Stop!");
break;
case TrafficLights.Yellow:
Console.WriteLine("Caution!");
break;
}
}
}
- Ví dụ:
enum Test { a=2, b, c, d, e };
static void Main(string[] args) {
int x = (int)Test.c;
Console.WriteLine(x);
}
In this C# code, you have defined an enumeration named Test with five members: a, b, c, d, and e. The enum assigns
integer values to each member starting from the value 2 for a, and then each subsequent member gets the next
integer value in sequence.

* Ngoại lệ (exception)
- Ngoại lệ là một vấn đề xảy ra trong quá trình thực thi chương trình. Ngoại lệ gây ra sự gián đoạn bất thường của
luồng thực thi của chương trình và làm cho chương trình kết thúc bất thường.
- Ngoại lệ có thể xảy ra vì nhiều lý do khác nhau. Một số ví dụ:
+ Người dùng nhập dữ liệu không hợp lệ.
+ Mở file không tồn tại.
+ Mất kết nối mạng trong quá trình giao tiếp.
+ Bộ nhớ không đủ và các vấn đề khác liên quan đến tài nguyên vật lý.
static void Main(string[] args)
{
int[] arr = new int[] { 4, 5, 8 };
Console.Write(arr[8]);
}
>> Như bạn thấy, ngoại lệ được gây ra bởi lỗi của người dùng, lỗi của lập trình viên hoặc các vấn đề liên quan đến tài
nguyên vật lý. Tuy nhiên, một chương trình hoạt động tốt nên xử lý tất cả các ngoại lệ có thể xảy ra.
* Xử lí ngoại lệ
- C# cung cấp một cơ chế linh hoạt gọi là lệnh try-catch giúp xử lý ngoại lệ để chương trình không bị dừng khi xảy ra
lỗi.
Các khối try và catch được sử dụng như sau:
try {
int[] arr = new int[] { 4, 5, 8 };
Console.Write(arr[8]);
}
catch(Exception e) {
Console.WriteLine("An error occurred");
}
- Đoạn code có thể tạo ra ngoại lệ được đặt trong khối try. Nếu một ngoại lệ xảy ra, khối catch được thực thi mà
không dừng chương trình.
- Loại ngoại lệ mà bạn muốn xử lý sẽ xuất hiện trong dấu ngoặc đơn theo sau từ khóa catch.
- Chúng ta sử dụng kiểu Exception chung để xử lý tất cả các loại ngoại lệ. Chúng ta cũng có thể sử dụng đối tượng
ngoại lệ e để truy cập chi tiết ngoại lệ, chẳng hạn như thông báo lỗi ban đầu (e.Message):
Console.WriteLine(e.Message);
>> Bạn cũng có thể bắt và xử lý các ngoại lệ khác nhau một cách độc lập.

* Xử lý nhiều ngoại lệ
- Một khối try có thể chứa nhiều khối catch xử lý các ngoại lệ khác nhau một cách riêng biệt.
- Việc xử lý ngoại lệ đặc biệt hữu ích khi xử lý dữ liệu đầu vào từ người dùng.
Ví dụ, đối với một chương trình yêu cầu đầu vào là hai số và in kết quả tính thương, hãy đảm bảo rằng bạn đã xử lý
trường hợp chia cho 0, trong trường hợp người dùng nhập 0 là số thứ hai.
int x, y;
try {
x = Convert.ToInt32(Console.Read());
y = Convert.ToInt32(Console.Read());
Console.WriteLine(x / y);
}
catch (DivideByZeroException e) {
Console.WriteLine("Cannot divide by 0");
}
catch(Exception e) {
Console.WriteLine("An error occurred");
}
- Đoạn code trên xử lý ngoại lệ DivideByZeroException một cách độc lập. Khối catch cuối cùng xử lý tất cả các ngoại lệ
khác có thể xảy ra. Nếu cần xử lý nhiều ngoại lệ, kiểu Exception phải được xác định cuối cùng.
- Bây giờ, nếu người dùng nhập số thứ hai là 0, "Cannot divide by 0" sẽ được hiển thị.
- Nếu người dùng nhập các giá trị không phải là số nguyên, "An error occurred" sẽ được hiển thị.
>> Sau đây là một số loại ngoại lệ được sử dụng phổ biến nhất: FileNotFoundException, FormatException,
IndexOutOfRangeException, InvalidOperationException, OutOfMemoryException.

* Khối finally
- Một khối finally tùy chọn có thể được sử dụng sau các khối catch. Khối finally chứa các câu lệnh được thực thi bất
kể có ngoại lệ xảy ra hay không.
>> Khối finally có thể được sử dụng khi bạn làm việc với các file hoặc tài nguyên khác. Khối finally đảm bảo rằng tài
nguyên được đóng hoặc giải phóng cho dù có ngoại lệ xảy ra hay không.

* Ghi vào File


- Không gian tên System.IO bao gồm nhiều lớp khác nhau được sử dụng để thực hiện các thao tác với file, chẳng hạn
như tạo và xóa file, đọc từ hoặc ghi vào file, đóng file và nhiều hơn nữa.
- Lớp File là một trong số đó.
string str = "Some text";
File.WriteAllText("test.txt", str);
Phương thức WriteAllText() tạo một file với đường dẫn được chỉ định và ghi nội dung vào đó. Nếu file đã tồn tại, nó
sẽ bị ghi đè.
Đoạn code trên tạo file test.txt và ghi nội dung của chuỗi str vào đó.
>> Để sử dụng lớp File, bạn cần sử dụng không gian tên System.IO: using System.IO;

* Đọc từ File
- Bạn có thể đọc nội dung của file bằng cách sử dụng phương thức ReadAllText của lớp File:
Phương thức này sẽ in ra nội dung của file test.txt.
Các phương thức có sẵn trong lớp File:
AppendAllText() - thêm văn bản vào cuối file.
Create() - tạo file tại một vị trí xác định.
Delete() - xóa file.
Exists() - kiểm tra xem file có tồn tại không.
Copy() - sao chép file vào một vị trí mới.
Move() - di chuyển file đến một vị trí mới
>> Tất cả các phương thức trên sẽ tự động đóng file sau khi thực hiện thao tác.

* Phương thức Generic


Phương thức Generic cho phép viết một phương thức có thể hoạt động với nhiều kiểu dữ liệu khác nhau mà không
cần phải viết lại code cho từng kiểu dữ liệu đó.

Ví dụ, hãy khai báo một phương thức hoán đổi giá trị của hai tham số:
static void Swap(ref int a, ref int b) {
int temp = a;
a = b;
b = temp;
}
- Phương thức Swap chỉ hoạt động với các tham số kiểu số nguyên. Nếu chúng ta muốn sử dụng phương thức với các
kiểu dữ liệu khác như double hay chuỗi, chúng ta phải nạp chồng nó cho tất cả các kiểu cần sử dụng. Ngoài code
trùng lặp rất nhiều, việc quản lý chương trình trở nên khó khăn vì thay đổi trong một phương thức đồng nghĩa với
việc thay đổi tất cả các phương thức nạp chồng.
- Phương thức Generic cung cấp một cơ chế linh hoạt để định nghĩa một kiểu chung (generic).
static void Swap<T>(ref T a, ref T b) {
T temp = a;
a = b;
b = temp;
}
- Trong đoạn code trên, T là tên của kiểu generic. Chúng ta có thể đặt tên bất kỳ, nhưng T là một tên thường được sử
dụng. Phương thức Swap hiện lấy hai tham số kiểu T. Chúng ta cũng sử dụng kiểu T cho biến temp được sử dụng để
hoán đổi các giá trị.
>> Lưu ý dấu ngoặc nhọn trong cú pháp <T> được sử dụng để định nghĩa một kiểu generic.
>> Nhiều tham số generic có thể được sử dụng trong một phương thức duy nhất.
Ví dụ: Func<T, U> lấy hai kiểu generic khác nhau.

* Lớp Generic
- Các kiểu generic cũng có thể được sử dụng với lớp.
- Lớp generic thường được sử dụng với tập hợp các đối tượng (collection), trong đó các thao tác như thêm và xóa các
mục khỏi collection được thực hiện theo cách tương đối giống nhau bất kể kiểu dữ liệu được lưu trữ là gì. Một trong
các loại collection là stack. Các mục được "đẩy vào" (thêm vào) bộ sưu tập, và "lấy ra" (loại bỏ khỏi) bộ sưu tập. Stack
đôi khi được gọi là cấu trúc dữ liệu Vào sau Ra trước - Last In First Out (LIFO).
- Lớp generic lưu trữ các phần tử trong một mảng. Như bạn thấy, kiểu generic T được sử dụng làm kiểu của mảng,
kiểu tham số cho phương thức Push và kiểu trả về cho các phương thức Pop và Get.
- Bây giờ chúng ta có thể tạo ra các đối tượng của lớp generic:
Stack<int> intStack = new Stack<int>();
Stack<string> strStack = new Stack<string>();
Stack<Person> PersonStack = new Stack<Person>();
>> Trong lớp generic, chúng ta không cần phải định nghĩa kiểu generic cho các phương thức của nó vì kiểu generic đã
được định nghĩa trên cấp độ lớp.

* Collection trong C#
- Collection được sử dụng để nhóm các đối tượng liên quan với nhau. Khác với mảng, collection là một cấu trúc dữ
liệu động và cũng có thể nhóm các đối tượng. Collection có thể tăng và giảm kích thước để chứa bất kỳ số lượng đối
tượng nào. Các lớp collection được tổ chức thành không gian tên và chứa các phương thức tích hợp để xử lý các
phần tử trong collection.
- Collection tổ chức các dữ liệu liên quan với nhau trong máy tính để cho phép sử dụng chúng một cách hiệu quả.
- Có nhiều loại collection khác nhau trong C# và mỗi loại có ứng dụng riêng của nó. Một số collection cũng được sử
dụng cho các tác vụ cụ thể. Ví dụ, Dictionary được sử dụng để đại diện cho các kết nối trên trang web xã hội (như
Twitter, Facebook), queue được sử dụng để tạo lịch trình nhiệm vụ, HashSet được sử dụng trong các thuật toán tìm
kiếm, v.v.
- Collection thường bao gồm các phương thức thêm, xóa và đếm đối tượng. Câu lệnh for và câu lệnh foreach được
sử dụng để lặp qua các collection. Vì collection là một lớp, bạn phải khai báo một thực thể của lớp trước khi thêm
các phần tử vào collection.
List<int> li = new List<int>();
>> Collection cho phép làm việc linh hoạt hơn với các nhóm đối tượng. Khác với mảng, nhóm các đối tượng mà bạn
làm việc có thể tăng và giảm kích thước khi nhu cầu của ứng dụng thay đổi.

* Generic Collection
- Generic collection là kiểu được ưu tiên sử dụng trong trường hợp mọi phần tử trong collection có cùng kiểu dữ liệu.
Chỉ có kiểu dữ liệu mong muốn mới có thể được thêm vào generic collection và điều này được bảo đảm bằng cách sử
dụng định kiểu mạnh, giúp giảm thiểu khả năng xảy ra lỗi.
- .NET Framework cung cấp một số lớp collection generic rất hữu ích cho việc lưu trữ và thao tác với dữ liệu.
- Không gian tên System.Collections.Generic bao gồm các generic collection sau:
List<T>
Dictionary<TKey, TValue>
SortedList<TKey, TValue>
Stack<T>
Queue<T>
Hashset<T>
- Để truy cập collection generic trong chương trình, bạn cần nhập lệnh: using Systems.Collections.Generic;
* Non-generic Collection
- có thể lưu trữ các mục có kiểu Object. Vì kiểu dữ liệu Object có thể tham chiếu đến bất kỳ kiểu dữ liệu nào, bạn có
nguy cơ gặp phải kết quả không mong muốn. Non-generic Collection cũng có thể gây chậm trễ trong việc truy cập và
thực thi.
- Không gian tên System.Collections bao gồm các non-generic collection sau:
ArrayList
SortedList
Stack
Queue
Hashtable
BitArray
>> Bởi vì non-generic collection dễ xảy ra lỗi và hiệu suất kém hơn nên bạn được khuyến khích sử dụng generic
collection từ không gian tên System.Collections.Generic nếu có sẵn và tránh sử dụng các collection kế thừa từ không
gian tên System.Collections.

* List<T>
Lý thuyết
List (danh sách) tương tự như mảng, nhưng các phần tử trong danh sách có thể được chèn và xóa động.
Lớp List<T> của generic collection C# yêu cầu tất cả các phần tử đều có cùng kiểu T.
Thuộc tính và phương thức List<T> bao gồm:
Count: Một thuộc tính trả về số lượng các phần tử trong danh sách.
Item[int i]: lấy hoặc thiết lập phần tử trong danh sách tại chỉ số i. Item là indexer và không bắt buộc phải có khi truy
cập một phần tử. Bạn chỉ cần sử dụng dấu ngoặc vuông [] và chỉ số bên trong dấu ngoặc vuông.
Add(T t): thêm phần tử t vào cuối danh sách.
RemoveAt(int index): xóa phần tử tại vị trí (chỉ số) xác định khỏi danh sách.
Sort(): sắp xếp các phần tử trong danh sách.
- Dưới đây là các thuộc tính và phương thức List<T> bổ sung. Hãy thử chúng bằng cách thêm chương trình ví dụ
List<T> ở trên.
Capacity - một thuộc tính trả về số lượng các phần tử mà danh sách có thể chứa trước khi cần thay đổi kích thước.
Clear() - xóa tất cả các phần tử khỏi danh sách.
TrimExcess() - giảm dung lượng của danh sách sao cho phù hợp với số lượng phần tử hiện có, giúp giảm thiểu sự lãng
phí bộ nhớ.
AddRange(IEnumerable coll) - thêm các phần tử của tập hợp coll có kiểu giống với List<T> vào cuối danh sách.
IEnumerable là một interface hỗ trợ việc lặp qua các phần tử trong collection.
Insert(int i, T t) - chèn phần tử t vào chỉ số i trong danh sách.
InsertRange(int i, IEnumerable coll) - chèn các phần tử của tập hợp coll vào chỉ số i trong danh sách. IEnumerable là
một interface hỗ trợ việc lặp qua các phần tử trong collection.
>> Nhớ rằng bạn cần nhập câu lệnh using Systems.Collections.Generic; để có thể sử dụng List<T>.

You might also like