Lời mở đầu
Là một .Net developer, mình nhận thấy có những khái niệm rất quan trọng để giúp chúng ta có thể up-level coding skill. Bài viết này là bài viết đầu tiên trong series Dotnet-Foundation mà mình sẽ chia sẻ với anh em. Trong series này mình sẽ tập trung giới thiệu với anh em các khái niệm, có thể là lý thuyết, có thể sẽ kèm theo code minh họa, hoặc các tài liệu gốc liên quan để giúp anh em dễ dàng nắm bắt những khái niệm vô cùng quan trọng này. Trong tương lai, mình sẽ hướng đến series System-Design In Practial, và chắc chắn sẽ là người thật việc thật, chúng ta sẽ áp dụng những lý thuyết, khái niệm này rất nhiều đó. Nhưng trước hết hãy cùng tìm hiểu xem những khái niệm mà mọi C#/.Net develop nên nắm được là gì nhé!!!
1.Kiểu dữ liệu cơ bản (Primitive Data Types) trong C#
Kiểu dữ liệu cơ bản là những kiểu được xây dựng sẵn trong ngôn ngữ C#, dùng để lưu trữ các giá trị đơn giản như số nguyên (int), số thực (float, double), ký tự (char), và giá trị logic (bool). Đây là nền tảng để xây dựng các cấu trúc phức tạp hơn.
- Số nguyên (int, long, short): Được lưu trữ trên stack, truy cập nhanh và không gây phân mảnh bộ nhớ.
- Số thực (float, double, decimal): Thường được dùng cho các tính toán liên quan đến số thực, nhưng decimal được ưu tiên khi xử lý tài chính vì độ chính xác cao hơn.
- Boolean (bool): Lưu trữ giá trị true hoặc false, thường dùng trong các cấu trúc điều kiện.
- Char: Đại diện cho một ký tự Unicode, lưu dưới dạng 16-bit.
using System;
class CircleAreaCalculator
{
static void Main()
{
const double Pi = 3.14159; // Hằng số Pi
double radius;
Console.WriteLine("Nhập bán kính hình tròn:");
radius = Convert.ToDouble(Console.ReadLine());
double area = Pi * radius * radius;
Console.WriteLine($"Diện tích hình tròn là: {area}");
}
}
Notes:
- Stack hoạt động hiệu quả hơn heap, do đó các kiểu giá trị thường được xử lý nhanh hơn kiểu tham chiếu.
- Chọn đúng kiểu dữ liệu sẽ giúp code bạn solid hơn và cũng phần nào đó có impact tốt đến perfomance.
2.Kiểu dữ liệu và biến (Data Types and Variables) trong C#
Biến trong C# là nơi lưu trữ dữ liệu tạm thời trong bộ nhớ. Hiểu rõ cách khai báo và sử dụng kiểu dữ liệu sẽ giúp tối ưu hóa hiệu suất và giảm lỗi.
- Biến có hai loại chính:
- Kiểu giá trị (Value Type): Lưu trữ trực tiếp giá trị.
- Kiểu tham chiếu (Reference Type): Lưu trữ địa chỉ tham chiếu đến giá trị trong heap.
- Biến có thể khai báo với từ khóa var để tự động suy luận kiểu.
using System;
class TypeConversionExample
{
static void Main()
{
string numberStr = "42";
int numberInt = int.Parse(numberStr); // Ép kiểu tường minh
double numberDouble = numberInt; // Ép kiểu ngầm định
Console.WriteLine($"Số nguyên: {numberInt}");
Console.WriteLine($"Số thực: {numberDouble}");
}
}
Notes:
- Ép kiểu ngầm định (implicit) không tốn thêm chi phí xử lý.
- Ép kiểu tường minh (explicit) có thể gây lỗi runtime nếu không kiểm tra trước.
- Sử dụng var có thể gây khó khăn trong việc đọc mã, nhưng không ảnh hưởng đến hiệu năng.
Best practices:
- Khai báo biến với phạm vi hẹp nhất để giảm chi phí bộ nhớ.
- Tránh sử dụng var nếu kiểu dữ liệu không rõ ràng, để mã nguồn dễ đọc hơn.
- Kiểm tra kiểu dữ liệu trước khi ép kiểu, đặc biệt với các kiểu không tương thích.
- Sử dụng biến cục bộ (local variable) thay vì biến toàn cục (global variable) để giảm tiêu thụ tài nguyên.
3.Toán tử và biểu thức (Operators and Expressions) trong C#
Toán tử là các ký hiệu dùng để thực hiện các phép toán trên biến và giá trị.
- Toán tử số học: +, -, *, /, % dùng để thực hiện các phép toán cơ bản.
- Toán tử so sánh: ==, !=, <, > kiểm tra quan hệ giữa hai giá trị.
- Toán tử logic: &&, ||, ! dùng để kết hợp điều kiện.
- Toán tử điều kiện (ternary): (condition) ? trueResult : falseResult là cách viết ngắn gọn cho cấu trúc if-else.
using System;
class OddEvenChecker
{
static void Main()
{
Console.WriteLine("Nhập một số:");
int number = int.Parse(Console.ReadLine());
string result = (number % 2 == 0) ? "chẵn" : "lẻ";
Console.WriteLine($"Số {number} là số {result}.");
}
}
Notes:
- Sử dụng toán tử số học như % có thể chậm hơn so với kiểm tra bit trong một số trường hợp cụ thể.
- Toán tử logic ngắn mạch (short-circuit) như &&, || cải thiện hiệu năng bằng cách ngừng kiểm tra khi không cần thiết.
Best practices:
- Ưu tiên các toán tử ngắn mạch (&&, ||) để giảm số lần thực thi không cần thiết.
- Tránh các biểu thức phức tạp trong điều kiện để tăng tính rõ ràng và hiệu năng.
- Dùng toán tử checked để phát hiện tràn số trong các phép toán số học.
4.Cấu trúc điều khiển (Control Structures: if-else, loops)
Cấu trúc điều khiển cho phép thực thi mã dựa trên điều kiện hoặc lặp lại mã nhiều lần.
- Cấu trúc điều kiện (if-else): Điều hướng luồng chương trình.
- Vòng lặp (for, while, do-while): Lặp lại khối mã dựa trên điều kiện.
- Vòng lặp foreach: Lặp qua các phần tử trong một tập hợp.
using System;
class OddSumCalculator
{
static void Main()
{
int sum = 0;
for (int i = 1; i <= 10; i++)
{
if (i % 2 != 0)
sum += i;
}
Console.WriteLine($"Tổng các số lẻ từ 1 đến 10 là: {sum}");
}
}
Notes:
- Vòng lặp for nhanh hơn foreach khi thao tác với mảng, do tránh được iterator.
- break và continue giúp giảm số lần lặp không cần thiết, cải thiện hiệu năng.
Best practices:
- Sử dụng break để thoát khỏi vòng lặp khi đạt điều kiện.
- Tránh sử dụng các vòng lặp lồng nhau sâu, thay bằng thuật toán tối ưu hơn.
- Khi lặp qua tập hợp lớn, cân nhắc dùng Parallel.For để cải thiện hiệu năng.
5.Phương thức và hàm (Methods and Functions)
Phương thức là khối mã thực hiện một nhiệm vụ cụ thể và có thể được gọi ở nhiều nơi trong chương trình, giúp tái sử dụng mã và tổ chức tốt hơn.
- Phương thức có giá trị trả về: Sử dụng từ khóa return để trả về giá trị.
- Phương thức không trả về giá trị: Dùng từ khóa void.
- Tham số:
- Tham số bắt buộc: Phải cung cấp giá trị khi gọi hàm.
- Tham số tùy chọn: Có giá trị mặc định.
- Đệ quy (Recursion): Một phương thức tự gọi chính nó.
using System;
class FactorialCalculator
{
static int Factorial(int n)
{
if (n == 0 || n == 1) return 1;
return n * Factorial(n - 1);
}
static void Main()
{
Console.Write("Nhập số cần tính giai thừa: ");
int num = int.Parse(Console.ReadLine());
Console.WriteLine($"Giai thừa của {num} là: {Factorial(num)}");
}
}
Notes:
- Đệ quy sử dụng stack để lưu trạng thái hàm, có thể dẫn đến lỗi tràn stack (stack overflow) nếu không kiểm soát tốt.
- Phương thức có tham số tùy chọn giảm số lượng quá tải (overload) cần thiết, giúp mã gọn hơn.
Best practices:
- Tránh sử dụng đệ quy nếu có thể thay bằng vòng lặp để tránh lỗi tràn stack.
- Dùng async và await khi xử lý các phương thức bất đồng bộ.
- Đặt tên phương thức rõ ràng, mô tả chính xác nhiệm vụ.
6.Lớp và đối tượng (Classes and Objects)
Lớp (class) là bản thiết kế cho các đối tượng, còn đối tượng (object) là thể hiện của lớp. Đây là nền tảng của lập trình hướng đối tượng.
- Lớp gồm:
- Thuộc tính (properties): Dùng để lưu trữ dữ liệu.
- Phương thức (methods): Dùng để thao tác dữ liệu.
- Đối tượng: Tạo từ lớp bằng từ khóa new.
- Tính đóng gói (Encapsulation): Hạn chế truy cập trực tiếp vào dữ liệu thông qua private và public.
using System;
class Car
{
public string Model { get; set; }
public string Color { get; set; }
public void Start()
{
Console.WriteLine($"{Color} {Model} đang khởi động.");
}
}
class Program
{
static void Main()
{
Car myCar = new Car { Model = "Toyota", Color = "Xanh" };
myCar.Start();
}
}
Notes:
- Đối tượng tạo trên heap cần được thu gom rác (garbage collected), dẫn đến chi phí quản lý bộ nhớ cao hơn.
- Tính đóng gói cải thiện bảo mật và giảm lỗi, nhưng có thể gây tăng chi phí truy cập dữ liệu.
Best practices:
- Dùng tính đóng gói (private, public, protected) để bảo vệ dữ liệu.
- Sử dụng từ khóa readonly cho các thuộc tính chỉ đọc.
- Tránh tạo các đối tượng không cần thiết để giảm áp lực cho bộ thu gom rác.
7.Kế thừa và đa hình (Inheritance and Polymorphism)
Kế thừa (Inheritance) cho phép một lớp con thừa hưởng thuộc tính và phương thức từ lớp cha. Đa hình (Polymorphism) cho phép một đối tượng có nhiều hình thức khác nhau.
- Kế thừa: Lớp con sử dụng từ khóa : để mở rộng lớp cha.
- Đa hình:
- Phương thức ảo (virtual): Có thể được ghi đè bởi lớp con.
- Phương thức trừu tượng (abstract): Lớp con bắt buộc phải triển khai.
using System;
class Animal
{
public virtual void Speak()
{
Console.WriteLine("Động vật kêu.");
}
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Chó sủa: Gâu gâu!");
}
}
class Program
{
static void Main()
{
Animal myDog = new Dog();
myDog.Speak(); // Gọi phương thức ghi đè của lớp Dog
}
}
Notes:
- Phương thức virtual và override cần thêm một bảng tra cứu (vtable), dẫn đến chậm hơn phương thức không ảo.
- Tính kế thừa không đúng cách có thể gây tăng độ phức tạp.
Best practices:
- Sử dụng sealed để ngăn lớp con kế thừa lớp khi không cần thiết.
- Dùng tính đa hình khi cần mở rộng linh hoạt, tránh ghi đè nếu không cần thiết.
- Không lạm dụng kế thừa, thay vào đó cân nhắc sử dụng thành phần (composition).
8.Interface và lớp trừu tượng (Interface and Abstract Classes)
Interface định nghĩa các hành vi mà lớp phải triển khai. Lớp trừu tượng (abstract) cung cấp một khung chung và có thể chứa logic mặc định.
- Interface:
- Chỉ định nghĩa phương thức, không có logic.
- Lớp có thể triển khai nhiều interface.
- Lớp trừu tượng:
- Có thể chứa phương thức trừu tượng hoặc logic sẵn.
- Lớp con chỉ kế thừa từ một lớp trừu tượng.
using System;
interface ILogger
{
void Log(string message);
}
class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"Log: {message}");
}
}
class Program
{
static void Main()
{
ILogger logger = new ConsoleLogger();
logger.Log("Hello, World!");
}
}
Notes:
- Gọi phương thức qua interface có chi phí cao hơn so với gọi trực tiếp.
- Lớp trừu tượng có thể sử dụng logic mặc định, giảm mã lặp.
Best practices:
- Sử dụng interface để tăng tính linh hoạt và khả năng mở rộng.
- Dùng lớp trừu tượng khi cần logic chung cho các lớp con.
- Tránh lạm dụng interface hoặc tạo quá nhiều lớp trừu tượng không cần thiết.
9.Xử lý ngoại lệ (Exception Handling)
Xử lý ngoại lệ trong C# giúp chương trình xử lý lỗi một cách an toàn và tránh bị dừng đột ngột.
- Ngoại lệ (Exception) là lỗi xảy ra khi chương trình đang chạy, chẳng hạn chia cho 0 hoặc truy cập mảng ngoài giới hạn.
- Khối try-catch-finally:
- try: Chứa mã có thể phát sinh lỗi.
- catch: Xử lý lỗi nếu xảy ra.
- finally: Thực thi mã bất kể lỗi có xảy ra hay không.
- Tạo ngoại lệ tùy chỉnh: Thừa kế từ lớp Exception.
using System;
class ExceptionHandlingExample
{
static void Main()
{
try
{
Console.Write("Nhập số bị chia: ");
int dividend = int.Parse(Console.ReadLine());
Console.Write("Nhập số chia: ");
int divisor = int.Parse(Console.ReadLine());
int result = dividend / divisor;
Console.WriteLine($"Kết quả: {result}");
}
catch (DivideByZeroException)
{
Console.WriteLine("Lỗi: Không thể chia cho 0.");
}
catch (FormatException)
{
Console.WriteLine("Lỗi: Vui lòng nhập số hợp lệ.");
}
finally
{
Console.WriteLine("Cảm ơn bạn đã sử dụng chương trình.");
}
}
}
Notes:
- Quá trình ném (throw) và bắt (catch) ngoại lệ tốn chi phí CPU. Nên hạn chế dùng ngoại lệ để kiểm tra điều kiện thông thường.
- Việc sử dụng nhiều khối catch làm tăng độ phức tạp và có thể ảnh hưởng đến hiệu năng.
Best practices:
- Chỉ sử dụng ngoại lệ cho các lỗi không thể tránh khỏi.
- Luôn sử dụng finally để giải phóng tài nguyên.
- Ghi log chi tiết lỗi để dễ dàng khắc phục.
10.Mảng và danh sách (Arrays and Lists)
Mảng (Array) và Danh sách (List) là các cấu trúc dữ liệu cơ bản để lưu trữ tập hợp phần tử trong C#.
- Mảng (Array):
- Kích thước cố định, lưu trữ các phần tử cùng kiểu dữ liệu.
- Truy cập nhanh bằng chỉ số (index).
- Danh sách (List
): - Kích thước động, linh hoạt thêm/xóa phần tử.
- Hỗ trợ nhiều phương thức LINQ.
using System;
using System.Collections.Generic;
class ArrayAndListExample
{
static void Main()
{
// Mảng
int[] array = { 1, 2, 3, 4, 5 };
int arraySum = 0;
foreach (var item in array)
{
arraySum += item;
}
Console.WriteLine($"Tổng mảng: {arraySum}");
// Danh sách
List<int> list = new List<int> { 1, 2, 3, 4, 5 };
list.Add(6); // Thêm phần tử
int listSum = 0;
foreach (var item in list)
{
listSum += item;
}
Console.WriteLine($"Tổng danh sách: {listSum}");
}
}
Notes:
- Mảng: Truy cập nhanh hơn danh sách do không cần xử lý thêm cấu trúc động.
- Danh sách: Tốn bộ nhớ hơn khi phải cấp phát lại (resize) trong quá trình thêm phần tử. Best practices:
- Sử dụng mảng khi biết trước kích thước dữ liệu.
- Dùng List
nếu cần linh hoạt thêm/xóa phần tử. - Tránh thay đổi kích thước mảng hoặc danh sách nhiều lần để giảm chi phí bộ nhớ.
Kết luận
Vậy là trong bài viết này mình đã giới thiệu với anh em 10 khái niệm mà mình nghĩ là cơ bản, và anh em cần nắm được. Tất nhiên phạm vi bài viết mang tính giới thiệu, lý thuyết sẽ hơi chán một chút. Nhưng tin mình đi, học môn võ công nào cũng vậy cần nắm chắc lý thuyết và thực hành đi, thực hành lại rất nhiều lần thì mới có thể tu luyện thành công. Trong bài viết tiếp theo mình sẽ tiếp tục giới thiệu thêm 10 khái niệm nữa, và mình nghĩ nó cũng sẽ nâng cao hơn một chút. Cùng đón xem nhé. Thanks for reading !!!