1166 từ
6 phút
C# concepts every Developer should know - part1

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ểuLưu ý
int, long, shortStack-allocated, không gây phân mảnh bộ nhớ
float, doubleTính toán nhanh nhưng có sai số
decimalChậm hơn double ~20x, nhưng bắt buộc cho tài chính
bool1 byte, không phải 1 bit
char16-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ụ intdouble) an toàn, không tốn chi phí.
  • Explicit casting có thể gây InvalidCastException — luôn dùng is hoặc as để kiểm tra trước.
  • Dùng var khi 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: &&|| 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:

  • for nhanh hơn foreach trên array vì bỏ qua enumerator overhead. Trên List<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.ForEach cho 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 set trừ khi có lý do cụ thể.
  • Dùng readonly cho 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 struct hoặc record struct cho 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: Dog is an Animal.
  • 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”.
  • sealed class 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#

InterfaceAbstract Class
Multiple inheritanceKhông
Default implementationCó (C# 8+)
Field/stateKhông
Dùng khiĐịnh nghĩa contractChia 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 injectiontestability. 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ể.
  • finally hoặc using để đả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>#

ArrayList<T>
Kích thướcCố địnhĐộng (tự resize)
Hiệu năngNhanh hơn (liên tục trong bộ nhớ)Chậm hơn ~5-10%
ResizeKhôngTự động, nhưng tốn chi phí copy
APIHạ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!

C# concepts every Developer should know - part1
https://www.devwithxuan.com/vi/posts/concepts-should-know-part1/
Tác giả
XuanPD
Ngày đăng
2024-05-01
Giấy phép
CC BY-NC-SA 4.0
Chia sẻ:

Đă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.

Bình luận