Chuyển tới nội dung chính

Types and Variables

Định nghĩa (Definition)

C# có một hệ thống kiểu thống nhất (Unified Type System) trong đó tất cả các kiểu đều kế thừa từ System.Object. Ngôn ngữ là kiểu mạnh (Strongly-typed): mọi biến, hằng số và biểu thức đều có kiểu được xác định tại thời điểm biên dịch, và trình biên dịch thực thi tính tương thích kiểu. C# phân biệt giữa kiểu giá trị (Value Types) (lưu trữ trực tiếp) và kiểu tham chiếu (Reference Types) (lưu trữ qua tham chiếu).

Các khái niệm cốt lõi (Core Concepts)

Kiểu giá trị (Value Types) vs Kiểu tham chiếu (Reference Types)

Khía cạnhKiểu giá trị (Value Types)Kiểu tham chiếu (Reference Types)
Lưu trữ trongStack (thường)Heap
ChứaDữ liệu trực tiếpTham chiếu (Con trỏ/Pointer) đến dữ liệu
Gán biếnSao chép giá trịSao chép tham chiếu
Mặc địnhKhông thể là null (trừ Nullable<T>)Có thể là null
Ví dụint, double, bool, char, struct, enumstring, object, class, interface, delegate, array
Kiểu cơ sởSystem.ValueTypeSystem.Object
Phân biệt chính

Gán một kiểu giá trị (Value Type) sao chép dữ liệu. Gán một kiểu tham chiếu (Reference Type) sao chép con trỏ — cả hai biến đều trỏ đến cùng một đối tượng trên Heap. Sửa đổi một biến sẽ ảnh hưởng đến biến kia.

Bảng kiểu tích hợp sẵn (Built-in Types Table)

Từ khóa C#Kiểu .NETKích thướcPhạm vi / Mô tả
intSystem.Int324 bytes-2,147,483,648 đến 2,147,483,647
longSystem.Int648 bytesSố nguyên rất lớn
shortSystem.Int162 bytes-32,768 đến 32,767
byteSystem.Byte1 byte0 đến 255
floatSystem.Single4 bytesĐộ chính xác ~7 chữ số (hậu tố f)
doubleSystem.Double8 bytesĐộ chính xác ~15-16 chữ số
decimalSystem.Decimal16 bytesĐộ chính xác 28-29 chữ số (hậu tố m)
boolSystem.Boolean1 bytetrue hoặc false
charSystem.Char2 bytesKý tự Unicode
stringSystem.StringBiến đổiVăn bản Unicode (kiểu tham chiếu!)
objectSystem.ObjectN/ACơ sở của mọi kiểu (kiểu tham chiếu!)

Biến (Variables)

Khai báo và khởi tạo:

int age = 30;
string name = "Alice";
bool isActive = true;
var score = 95.5; // Compiler infers double
var productName = "Widget"; // Compiler infers string

Từ khóa var cho phép trình biên dịch suy luận kiểu từ biểu thức khởi tạo. Nó không phải là kiểu biến đổi (Variant) hay kiểu động (Dynamic) — biến vẫn có kiểu mạnh (Strongly Typed) tại thời điểm biên dịch.

Quy ước đặt tên:

Phong cáchSử dụng choVí dụ
camelCaseBiến cục bộ (Local Variables), tham số (Parameters), trường riêng (Private Fields)orderCount, firstName
PascalCaseLớp (Classes), phương thức (Methods), thuộc tính (Properties), trường công khai (Public Fields)OrderService, TotalPrice
_camelCaseTrường riêng (Private Fields) - quy ước thay thế_orderCount, _repository
IPascalCaseGiao diện (Interfaces)IDisposable, IRepository

Hằng số (Constants): const vs readonly vs static readonly

Từ khóaKhi thiết lậpỞ đâuGhi chú
constThời điểm biên dịch (Compile Time)Trường (Fields) hoặc biến cục bộ (Locals)Giá trị được nhúng trong mã gọi; chỉ hỗ trợ kiểu nguyên thủy
readonlyThời gian chạy (Runtime - trong constructor)Trường thực thể (Instance Fields)Giá trị theo từng thực thể, thiết lập trong constructor
static readonlyThời gian chạy (Runtime - trong static constructor hoặc initializer)Trường tĩnh (Static Fields)Chia sẻ giữa mọi thực thể; có thể chứa đối tượng phức tạp
public class Configuration
{
public const int MaxRetries = 3; // Compile-time constant
public readonly string _instanceName; // Set in constructor
public static readonly DateTime LaunchDate // Set once at runtime
= DateTime.UtcNow;
}
const và phiên bản (Versioning)

Giá trị const được nhúng inline tại thời điểm biên dịch. Nếu bạn thay đổi một const trong thư viện, mã gọi được biên dịch với giá trị cũ sẽ vẫn sử dụng giá trị cũ cho đến khi biên dịch lại. Sử dụng static readonly cho các giá trị có thể thay đổi giữa các phiên bản.

Toán tử (Operators)

Số học (Arithmetic): +, -, *, /, %, ++, --

So sánh (Comparison): ==, !=, <, >, <=, >=

Logic (Logical): &&, ||, !

Toán tử xử lý null (Null-handling Operators):

// Null-coalescing — trả về vế trái nếu khác null, ngược lại trả về vế phải
string name = input ?? "default";

// Null-conditional — trả về null nếu toán hạng là null (thay vì ném ngoại lệ)
int? length = name?.Length;

// Null-coalescing assignment — chỉ gán nếu biến là null
name ??= "unnamed";

Ép kiểu (Type Casting)

Cách tiếp cậnMô tảVí dụ
Ẩn (Implicit)An toàn, không mất dữ liệu; tự động bởi trình biên dịchint → long, float → double
Rõ ràng (Explicit cast)Có thể mất dữ liệu; yêu cầu cú pháp (Type)(int)3.143
Lớp ConvertChuyển đổi giữa các kiểu cơ sởConvert.ToInt32("42")
ParsePhân tích chuỗi; ném ngoại lệ khi thất bạiint.Parse("42")
TryParsePhân tích chuỗi; trả về boolint.TryParse("42", out int result)
// Implicit — chuyển đổi mở rộng an toàn
int x = 100;
long big = x; // int → long, no cast needed

// Explicit — thu hẹp, có thể mất dữ liệu
double pi = 3.14159;
int truncated = (int)pi; // 3

// TryParse — phân tích an toàn
if (int.TryParse(userInput, out int result))
{
Console.WriteLine($"Parsed: {result}");
}

Boxing và Unboxing

Boxing là chuyển đổi một kiểu giá trị (Value Type) thành kiểu tham chiếu (Reference Type) (object hoặc interface). Giá trị được bọc trong một đối tượng trên Heap.

Unboxing là trích xuất kiểu giá trị (Value Type) từ đối tượng đã boxing bằng ép kiểu rõ ràng (Explicit Cast).

int number = 42;
object boxed = number; // Boxing — value copied to heap
int unboxed = (int)boxed; // Unboxing — value copied back to stack
Hiệu năng Boxing

Boxing cấp phát bộ nhớ trên Heap và gây áp lực GC. Tránh sử dụng trong các đường dẫn nóng (Hot Paths). Sử dụng generics (List<int> thay vì ArrayList) và tránh tham số object khi có thể.

Ví dụ mã (Code Examples)

Hành vi kiểu giá trị vs kiểu tham chiếu (Value vs Reference Type Behavior)

// Value type — assignment copies the value
int a = 10;
int b = a;
b = 20;
Console.WriteLine(a); // 10 — unchanged

// Reference type — assignment copies the reference
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1;
list2.Add(4);
Console.WriteLine(list1.Count); // 4 — affected!

Kiểu giá trị tùy chỉnh (Custom Value Type - struct)

public struct Point
{
public double X { get; }
public double Y { get; }

public Point(double x, double y) => (X, Y) = (x, y);

public double DistanceTo(Point other) =>
Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2));
}

Phân tích an toàn với TryParse (Safe Parsing with TryParse)

public int? ParseAge(string input)
{
if (int.TryParse(input, out int age) && age is >= 0 and <= 150)
{
return age;
}
return null;
}

Khi nào sử dụng (When to Use)

Tình huốngKhuyến nghị
Tính toán tài chính (Financial Calculations)Sử dụng decimal — tránh lỗi làm tròn số dấu phẩy động
Khoa học / đồ họaSử dụng double — hiệu năng và độ chính xác đủ dùng
Phân tích đầu vào người dùng (User Input)Luôn ưu tiên TryParse hơn Parse
Giá trị cấu hình bất biến (Immutable Configuration)Sử dụng const cho hằng số thực sự; static readonly cho các trường hợp khác
Cấu trúc dữ liệu nhỏ (16 bytes trở xuống)Cân nhắc struct để cấp phát trên Stack và không có overhead GC

Lỗi thường gặp (Common Pitfalls)

  • Struct có thể thay đổi (Mutable Structs) — struct là kiểu giá trị; sửa đổi thuộc tính thông qua phương thức trả về struct sẽ sửa đổi một bản sao. Hãy làm cho struct bất biến (Immutable).
  • == vs Equals cho chuỗi (Strings)== so sánh giá trị cho chuỗi (toán tử đã nạp chồng), nhưng cho các kiểu tham chiếu khác nó so sánh tham chiếu. Sử dụng string.Equals(a, b, StringComparison.Ordinal) để so sánh rõ ràng, an toàn về culture.
  • decimal vs double cho tiền tệdouble có lỗi làm tròn (0.1 + 0.2 != 0.3). Luôn sử dụng decimal cho giá trị tài chính.
  • var và khả năng đọc — sử dụng var khi kiểu rõ ràng từ vế phải (var stream = new FileStream(...)). Tránh khi kiểu không rõ ràng (var result = GetData()).
  • Boxing trong tập hợp không generic (Non-Generic Collections)ArrayList, Hashtable lưu trữ object, gây boxing cho mọi kiểu giá trị. Sử dụng List<T>, Dictionary<TKey, TValue>.

Tóm tắt要点 (Key Takeaways)

  1. C# có hệ thống kiểu thống nhất (Unified Type System) — mọi thứ đều kế thừa từ System.Object.
  2. Kiểu giá trị (Value Types) nằm trên Stack (thường); kiểu tham chiếu (Reference Types) nằm trên Heap với tham chiếu trên Stack.
  3. Ngữ nghĩa gán khác nhau: kiểu giá trị sao chép dữ liệu; kiểu tham chiếu sao chép tham chiếu.
  4. Sử dụng decimal cho tiền tệ, TryParse cho đầu vào người dùng và generics để tránh boxing.
  5. const là thời điểm biên dịch (Compile Time); readonly là thời gian chạy (Runtime) — chọn dựa trên nhu cầu phiên bản (Versioning).

Câu hỏi phỏng vấn (Interview Questions)

Q: Boxing là gì?

Boxing là quá trình chuyển đổi một kiểu giá trị (Value Type) thành kiểu tham chiếu (Reference Type) bằng cách bọc nó trong System.Object trên Heap. Điều này gây cấp phát Heap và overhead GC. Unboxing trích xuất giá trị bằng ép kiểu rõ ràng (Explicit Cast). Sử dụng generics để tránh boxing.

Q: Sự khác biệt giữa kiểu giá trị (Value Types) và kiểu tham chiếu (Reference Types) là gì?

Kiểu giá trị (Value Types) lưu trữ dữ liệu trực tiếp (thường trên Stack) và được sao chép khi gán. Kiểu tham chiếu (Reference Types) lưu trữ con trỏ đến dữ liệu trên Heap — phép gán sao chép tham chiếu, không phải dữ liệu. Kiểu giá trị kế thừa từ System.ValueType; kiểu tham chiếu kế thừa trực tiếp từ System.Object.

Q: Sự khác biệt giữa constreadonly là gì?

Giá trị const được thiết lập tại thời điểm biên dịch (Compile Time) và nhúng vào mã gọi — chúng không thể chứa đối tượng phức tạp và thay đổi yêu cầu biên dịch lại mã gọi. Giá trị readonly được thiết lập trong thời gian chạy (Runtime - trong constructor) và có thể chứa bất kỳ kiểu nào. Sử dụng static readonly cho hằng số runtime dùng chung.

Q: Từ khóa var là gì?

var cho phép nhập kiểu ẩn (Implicit Typing) — trình biên dịch suy luận kiểu thực tế từ biểu thức khởi tạo. Biến vẫn có kiểu mạnh (Strongly Typed) tại thời điểm biên dịch; var không phải kiểu động (Dynamic). Nó cải thiện khả năng đọc khi kiểu rõ ràng nhưng có thể làm giảm tính rõ ràng khi vế phải mơ hồ.

Tài liệu tham khảo (References)