Professional Documents
Culture Documents
* 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
* 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
}
* 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ố.
* 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.
* 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.
* 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);
* 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];
* 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.
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:
- 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ó:
- 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 đó.
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.
* 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.
* Đọ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.
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>.