Tại sao cần nắm vững nền tảng?
Nhiều bug production và bottleneck hiệu năng trong C# bắt nguồn từ việc hiểu sai các khái niệm cơ bản — value type vs reference type, boxing, exception flow. Bài viết này tổng hợp 10 khái niệm nền tảng mà bạn sẽ dùng hàng ngày, kèm theo những lưu ý thực tế mà documentation ít nhắc đến.
Đây là bài đầu tiên trong series Dotnet-Foundation.
1. Primitive Data Types
Kiểu dữ liệu cơ bản lưu trữ giá trị đơn giản, nằm trên stack, truy cập nhanh.
| Kiểu | Lưu ý |
|---|---|
int, long, short | Stack-allocated, không gây phân mảnh bộ nhớ |
float, double | Tính toán nhanh nhưng có sai số |
decimal | Chậm hơn double ~20x, nhưng bắt buộc cho tài chính |
bool | 1 byte, không phải 1 bit |
char | 16-bit Unicode |
Takeaway: Chọn decimal cho tiền tệ, double cho phần còn lại. Đừng dùng float trừ khi bạn cần tiết kiệm bộ nhớ (game, graphics).
2. Value Type vs Reference Type
Đây là khái niệm quan trọng nhất trong bài — hiểu sai sẽ tạo ra bug khó debug.
- Value type (
struct,int,bool…): Copy giá trị khi gán. Sống trên stack. - Reference type (
class,string,object): Copy con trỏ khi gán. Dữ liệu sống trên heap.
int a = 5;
int b = a; // b là bản copy, thay đổi b không ảnh hưởng a
var car1 = new Car { Model = "Toyota" };
var car2 = car1; // car2 trỏ cùng object
car2.Model = "Honda";
// car1.Model cũng là "Honda" — đây là nguồn gốc của nhiều bug
Takeaway:
- Implicit casting (ví dụ
int→double) an toàn, không tốn chi phí. - Explicit casting có thể gây
InvalidCastException— luôn dùngishoặcasđể kiểm tra trước. - Dùng
varkhi kiểu đã rõ ràng từ vế phải:var list = new List<string>()— OK.var result = GetData()— không rõ ràng.
3. Operators — Những điều hay bị bỏ qua
Toán tử cơ bản thì ai cũng biết. Đây là những điểm thực sự cần nhớ:
Short-circuit evaluation: && và || dừng sớm khi biết kết quả.
// obj.Name sẽ KHÔNG bị gọi nếu obj == null
if (obj != null && obj.Name == "test") { }
Checked arithmetic: Mặc định C# im lặng khi tràn số.
int max = int.MaxValue;
int overflow = max + 1; // = -2147483648, không báo lỗi!
int safe = checked(max + 1); // throw OverflowException
Takeaway: Dùng checked block trong code xử lý tài chính hoặc bất kỳ nơi nào tràn số có thể gây hậu quả nghiêm trọng.
4. Control Structures
Không cần giải thích if-else hay for loop. Đây là những điều đáng nhớ hơn:
fornhanh hơnforeachtrên array vì bỏ qua enumerator overhead. TrênList<T>, sự khác biệt không đáng kể.- Nested loop sâu > 2 tầng → tín hiệu cần refactor (tách method, dùng LINQ, hoặc lookup dictionary).
Parallel.For/Parallel.ForEachcho tập dữ liệu lớn, nhưng chỉ khi mỗi iteration độc lập.
// Thay vì nested loop O(n²)
var lookup = items.ToDictionary(x => x.Id);
foreach (var order in orders)
{
if (lookup.TryGetValue(order.ItemId, out var item))
Process(order, item);
}
Takeaway: Hãy nghĩ về độ phức tạp thuật toán trước khi nghĩ đến for vs foreach.
5. Methods — Tổ chức code hiệu quả
Nguyên tắc cốt lõi:
- Một method làm một việc. Nếu cần dùng “and” để mô tả method, hãy tách nó.
- Ưu tiên tham số ít — quá 3 tham số thì cân nhắc dùng object/record.
- Đệ quy nghe hay nhưng dễ gây
StackOverflowException. Luôn cân nhắc chuyển sang vòng lặp.
// Đệ quy — đẹp nhưng nguy hiểm với input lớn
static int Factorial(int n) =>
n <= 1 ? 1 : n * Factorial(n - 1);
// Iterative — an toàn hơn
static int FactorialSafe(int n)
{
int result = 1;
for (int i = 2; i <= n; i++) result *= i;
return result;
}
Takeaway: Đặt tên method rõ ràng (CalculateTotal, không phải DoStuff). Dùng async/await cho I/O-bound work, không phải CPU-bound.
6. Classes and Objects
Class = blueprint, Object = instance. Nhưng điều thực sự quan trọng là encapsulation:
class BankAccount
{
public string Owner { get; }
public decimal Balance { get; private set; } // Chỉ class mới thay đổi được
public BankAccount(string owner, decimal initial)
{
Owner = owner;
Balance = initial;
}
public void Deposit(decimal amount)
{
if (amount <= 0) throw new ArgumentException("Amount must be positive");
Balance += amount;
}
}
Takeaway:
- Mọi property nên
private settrừ khi có lý do cụ thể. - Dùng
readonlycho field không đổi sau constructor. - Object sống trên heap → tạo nhiều object nhỏ, tồn tại ngắn = áp lực lên GC. Cân nhắc
structhoặcrecord structcho data nhỏ.
7. Inheritance vs Composition
Kế thừa thì dễ hiểu, nhưng câu hỏi quan trọng hơn: khi nào dùng và khi nào không?
// Polymorphism qua virtual/override
class Animal
{
public virtual string Speak() => "...";
}
class Dog : Animal
{
public override string Speak() => "Gâu gâu!";
}
Animal pet = new Dog();
pet.Speak(); // "Gâu gâu!" — runtime dispatch qua vtable
Khi nào dùng inheritance:
- Quan hệ “is-a” rõ ràng:
Dogis anAnimal. - Cần polymorphism.
Khi nào dùng composition:
- Hầu hết các trường hợp còn lại. “Has-a” > “Is-a”.
sealedclass khi bạn không muốn ai kế thừa — giúp JIT optimize tốt hơn.
Takeaway: Mặc định chọn composition. Chỉ dùng inheritance khi polymorphism thực sự cần thiết.
8. Interface vs Abstract Class
| Interface | Abstract Class | |
|---|---|---|
| Multiple inheritance | Có | Không |
| Default implementation | Có (C# 8+) | Có |
| Field/state | Không | Có |
| Dùng khi | Định nghĩa contract | Chia sẻ logic giữa các lớp liên quan |
interface ILogger
{
void Log(string message);
}
// Dễ dàng swap implementation — đây là sức mạnh thực sự của interface
ILogger logger = environment.IsProduction
? new CloudLogger()
: new ConsoleLogger();
Takeaway: Interface phục vụ dependency injection và testability. Nếu bạn không biết chọn gì, chọn interface.
9. Exception Handling
Quy tắc vàng: Exception dành cho exceptional situations, không phải flow control.
// SAI — dùng exception để kiểm tra điều kiện bình thường
try {
var value = dictionary[key];
} catch (KeyNotFoundException) {
// handle missing key
}
// ĐÚNG — kiểm tra trước
if (dictionary.TryGetValue(key, out var value))
{
// use value
}
Chi phí thực tế: Throw + catch tốn ~10,000x so với một if check. Trong hot path, điều này tạo ra khác biệt rõ ràng.
Takeaway:
- Dùng
TryParse,TryGetValue… thay vì try-catch khi có thể. finallyhoặcusingđể đảm bảo giải phóng tài nguyên.- Log đầy đủ exception (message + stack trace), đừng chỉ swallow.
10. Array vs List<T>
| Array | List<T> | |
|---|---|---|
| Kích thước | Cố định | Động (tự resize) |
| Hiệu năng | Nhanh hơn (liên tục trong bộ nhớ) | Chậm hơn ~5-10% |
| Resize | Không | Tự động, nhưng tốn chi phí copy |
| API | Hạn chế | Phong phú (Add, Remove, LINQ…) |
// Biết trước size → dùng array
int[] scores = new int[100];
// Cần linh hoạt → dùng List<T> với capacity hint
var names = new List<string>(capacity: 50); // Tránh resize nhiều lần
Takeaway: 99% trường hợp dùng List<T>. Dùng array khi cần hiệu năng tối đa (game loop, số liệu lớn). Luôn set capacity nếu biết trước số lượng phần tử.
Tổng kết
10 khái niệm này là nền tảng — nhưng nền tảng vững thì build gì cũng nhanh. Bài tiếp theo trong series sẽ đi vào các khái niệm nâng cao hơn: generics, delegates, LINQ, async/await.
Nếu bạn thấy hữu ích, hãy chia sẻ cho đồng nghiệp. Thanks for reading!
Đăng ký nhận bản tin
Nhận thông báo khi có bài viết mới. Không spam, hủy bất cứ lúc nào.
