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

Anonymous & Nullable Types

Kiểu Vô danh (Anonymous Types)

Định nghĩa (Definition)

Kiểu vô danh (anonymous type) là kiểu không tên được tạo nội tuyến sử dụng từ khóa new với cú pháp khởi tạo đối tượng. Trình biên dịch tạo một lớp với các thuộc tính chỉ đọc và suy luận kiểu từ các giá trị được gán. Chúng thường được sử dụng trong phép chiếu LINQ (LINQ projection) và định hình dữ liệu ngắn hạn.

Tạo Kiểu Vô danh (Creating Anonymous Types)

// The compiler creates a class with Name (string) and Age (int) properties
var person = new { Name = "John", Age = 32 };

// Access properties like any other object
Console.WriteLine($"{person.Name}, {person.Age}"); // John, 32

Từ khóa var (The var Keyword)

Kiểu vô danh không có tên đã biết tại thời điểm biên dịch, nên bạn phải sử dụng var để giữ chúng. Từ khóa var báo cho trình biên dịch suy luận kiểu từ biểu thức khởi tạo:

var number = 15; // int
var word = "example"; // string
var money = 987.32; // double

Đặc điểm Chính (Key Characteristics)

  • Thuộc tính chỉ đọc (Read-only properties) — giá trị không thể thay đổi sau khi tạo
  • Trình biên dịch tạo (Compiler-generated) — trình biên dịch tạo lớp ẩn với Equals, GetHashCode, và ToString
  • Suy luận kiểu (Type inference) — kiểu thuộc tính được suy luận từ giá trị
  • Giới hạn phạm vi (Scope-limited) — chủ yếu sử dụng trong một phương thức; không thể truyền làm kiểu trả về (sử dụng tuple hoặc record thay thế)
var p1 = new { Name = "Alice", Age = 30 };
var p2 = new { Name = "Alice", Age = 30 };

// Two anonymous objects with the same property names, types, and values are equal
Console.WriteLine(p1.Equals(p2)); // True

// But they are not the same reference
Console.WriteLine(ReferenceEquals(p1, p2)); // False

Trường hợp Sử dụng Phổ biến: Phép chiếu LINQ (Common Use: LINQ Projections)

var results = products
.Select(p => new { p.Name, p.Price })
.Where(x => x.Price > 100);

foreach (var item in results)
{
Console.WriteLine($"{item.Name}: ${item.Price}");
}
Ưu tiên tuple cho kiểu trả về phương thức (Prefer tuples for method returns)

Kiểu vô danh không thể vượt qua ranh giới phương thức làm kiểu trả về. Nếu cần trả về kết quả đã định hình từ phương thức, hãy sử dụng tuple (string Name, decimal Price) hoặc kiểu record thay thế.


Kiểu Nullable (Nullable Types)

Định nghĩa (Definition)

Kiểu nullable (nullable type) đại diện cho các giá trị cũng có thể là null. C# hỗ trợ hai loại: kiểu giá trị nullable (nullable value type) (int?, bool?) bao bọc System.Nullable<T>, và kiểu tham chiếu nullable (nullable reference type) (C# 8+), một tính năng thời điểm biên dịch chú thích liệu kiểu tham chiếu có thể là null hay không. Cùng nhau, chúng tạo nên cách tiếp cận an toàn null (null safety) của C#.

Kiểu Giá trị Nullable (Nullable Value Types)

Kiểu giá trị nullable T? có thể giữ mọi giá trị của kiểu cơ sở T cộng thêm null. Trình biên dịch dịch T? thành System.Nullable<T>:

int? age = null;
int? score = 85;

// Behind the scenes: Nullable<int>
Console.WriteLine(age.HasValue); // False
Console.WriteLine(score.HasValue); // True
Console.WriteLine(score.Value); // 85

Thuộc tính HasValue và Value (HasValue and Value Properties)

int? count = GetCount();

if (count.HasValue)
{
int value = count.Value; // Access the underlying int
Console.WriteLine($"Count: {value}");
}

// Accessing .Value when HasValue is false throws InvalidOperationException

Chuyển đổi Nullable sang Non-Nullable (Nullable to Non-Nullable Conversion)

int? maybe = 42;

// .Value — throws if null
int a = maybe.Value;

// .GetValueOrDefault() — returns default (0) if null
int b = maybe.GetValueOrDefault();

// .GetValueOrDefault(fallback) — returns fallback if null
int c = maybe.GetValueOrDefault(-1);

// Null-coalescing operator — returns right side if left is null
int d = maybe ?? 0;

// GetValueOrDefault() is the safest approach when you want a default

Số học Nullable (Nullable Arithmetic)

Mọi phép toán số học hoặc so sánh liên quan đến null đều tạo ra null:

int? x = 10, y = null;

int? sum = x + y; // null
int? product = x * y; // null
bool? equal = x == y; // null (three-valued logic)

// Comparisons with null are false (except !=)
Console.WriteLine(x > y); // false
Console.WriteLine(x == y); // false
Console.WriteLine(x != y); // true

Kiểu Tham chiếu Nullable (Nullable Reference Types - C# 8+)

Kiểu tham chiếu nullable (nullable reference type) là tính năng phân tích thời điểm biên dịch — không phải thay đổi thời điểm chạy. Khi bật, trình biên dịch cảnh báo khi tham chiếu có thể là null được giải tham chiếu (dereference):

#nullable enable

string name = "Alice"; // Non-nullable — cannot be null
string? nickname = null; // Nullable — can be null

// Compiler warning CS8602: possible null reference
Console.WriteLine(nickname.Length);

// Safe access with null-conditional
Console.WriteLine(nickname?.Length); // null (no warning)
Console.WriteLine(nickname?.Length ?? 0); // 0

Cách hoạt động của Kiểu Tham chiếu Nullable (How Nullable Reference Types Work)

Trình biên dịch thực hiện phân tích trạng thái null (null state analysis) trên mỗi biến tham chiếu:

Trạng thái (State)Ý nghĩa (Meaning)
Not null (Không null)Chắc chắn được gán giá trị khác null (Definitely assigned a non-null value)
Maybe null (Có thể null)Có thể là null tại thời điểm này (Could be null at this point)
#nullable enable

void Process(string? input)
{
// input is "maybe null" here
if (input is null)
return;

// input is "not null" here — compiler knows
Console.WriteLine(input.Length); // No warning
}
string? name = GetName();

// ?. — null-conditional operator: returns null if operand is null
int? length = name?.Length;

// ?? — null-coalescing operator: returns right side if left is null
int safeLength = name?.Length ?? 0;

// ??= — null-coalescing assignment: assigns only if left is null
name ??= "Unknown";

// ! — null-forgiving operator: tells compiler "I know this isn't null"
// Does NOT do a runtime check — only suppresses the warning
int forceLength = name!.Length;
Toán tử ! không kiểm tra null (The ! operator does not check for null)

Toán tử bỏ qua null (null-forgiving operator) (!) chỉ triệt tiêu cảnh báo trình biên dịch. Nó không thêm kiểm tra null thời điểm chạy. Nếu giá trị thực sự là null, bạn vẫn sẽ nhận được NullReferenceException thời điểm chạy.

Bật Kiểu Tham chiếu Nullable (Enabling Nullable Reference Types)

<!-- Project level — .csproj -->
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
// File level
#nullable enable

// Disable for a section
#nullable disable
string legacy = null; // No warning
#nullable enable

// Restore to project setting
#nullable restore

Thuộc tính Nullable (Nullable Attributes)

Trình biên dịch sử dụng thuộc tính (attribute) để hiểu trạng thái null qua ranh giới phương thức:

// Return value is not null when the method returns true
bool TryGetValue([NotNullWhen(true)] out string? value);

// Parameter may be null when the method returns false
[return: MaybeNullWhen(false)]
T Find<T>(Predicate<T> match);

// Parameter is not null after the method returns
void EnsureCapacity([NotNull] ref string? value);

// Parameter will not be null if the return value is not null
[return: NotNullIfNotNull(nameof(input))]
string? Transform(string? input);
Thuộc tính (Attribute)Ý nghĩa (Meaning)
[NotNullWhen(true)]Tham số không null khi phương thức trả về true (Parameter is not null when method returns true)
[NotNullWhen(false)]Tham số không null khi phương thức trả về false (Parameter is not null when method returns false)
[MaybeNullWhen(true)]Tham số có thể null khi phương thức trả về true (Parameter may be null when method returns true)
[NotNull]Đầu ra được đảm bảo không null (Output is guaranteed not null)
[NotNullIfNotNull]Giá trị trả về không null nếu tham số chỉ định không null (Return is not null if the specified parameter is not null)
[MemberNotNullWhen]Thành viên được chỉ định không null khi phương thức trả về giá trị (Named member is not null when method returns the value)

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

  • Giá trị cơ sở dữ liệu — ánh xạ cột nullable sang kiểu nullable (int?, DateTime?)
  • Ranh giới API — kiểu tham chiếu nullable thực thi hợp đồng null tại thời điểm biên dịch
  • Tham số tùy chọn — sử dụng T? khi null có nghĩa là "không được cung cấp"
  • Di chuyển mã cũ — bật NRT (Nullable Reference Types) từng bước với #nullable enable cho từng tệp

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

Tắt cảnh báo thay vì sửa lỗi (Disabling warnings instead of fixing)

Đừng sử dụng ! hoặc #nullable disable để làm im cảnh báo mà không hiểu trạng thái null. Mỗi cảnh báo bị triệt tiêu là một NullReferenceException tiềm năng thời điểm chạy. Hãy sửa nguyên nhân gốc: thêm kiểm tra null, cung cấp giá trị mặc định, hoặc thay đổi kiểu.

Trộn nullable và non-nullable trong API (Mixing nullable and non-nullable in APIs)

Nếu API công khai của bạn nhận string nhưng triển khai có thể tạo ra null, người gọi sẽ không mong đợi null. Hãy làm cho chữ ký trung thực: sử dụng string? nếu null là có thể. Kiểu tham chiếu nullable làm cho các hợp đồng này trở nên rõ ràng.

Di chuyển từng bước (Migrate incrementally)

Đối với cơ sở mã lớn, bật kiểu tham chiếu nullable từng tệp hoặc từng dự án. Sử dụng #nullable enable ở đầu các tệp bạn muốn di chuyển và sửa cảnh báo trước khi mở rộng sang thêm tệp.

Tóm tắt (Key Takeaways)

  1. Kiểu vô danh (anonymous type) là lớp chỉ đọc do trình biên dịch tạo, được tạo nội tuyến với new { ... }.
  2. Sử dụng var để giữ instance kiểu vô danh vì tên kiểu không biết tại thời điểm biên dịch.
  3. T? trên kiểu giá trị tạo Nullable<T> với .HasValue.Value.
  4. T? trên kiểu tham chiếu là chú thích thời điểm biên dịch — không có thay đổi thời điểm chạy.
  5. Toán tử kết hợp null (null-coalescing operator) ?? và toán tử điều kiện null (null-conditional operator) ?. là công cụ chính cho mã an toàn null.
  6. Kiểu tham chiếu nullable với #nullable enable bắt lỗi null tại thời điểm biên dịch.
  7. Toán tử ! triệt tiêu cảnh báo nhưng không thêm kiểm tra thời điểm chạy — sử dụng ít.
  8. Thuộc tính nullable ([NotNullWhen], v.v.) giúp trình biên dịch hiểu hợp đồng phương thức.

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

Câu hỏi: Kiểu vô danh (anonymous type) trong C# là gì? Kiểu vô danh là lớp do trình biên dịch tạo không có tên tường minh, được tạo bằng cú pháp new { Property = value }. Thuộc tính chỉ đọc và kiểu của chúng được suy luận từ giá trị được gán. Bạn phải sử dụng var để giữ instance. Chúng thường được sử dụng trong phép chiếu LINQ (LINQ projection) để định hình dữ liệu.

Câu hỏi: Bạn có thể trả về kiểu vô danh từ phương thức không? Không, kiểu vô danh không thể được sử dụng làm kiểu trả về phương thức vì tên kiểu do trình biên dịch tạo và không thể truy cập trong mã. Sử dụng tuple (string Name, int Age) hoặc kiểu record thay thế khi cần trả về dữ liệu đã định hình từ phương thức.

Câu hỏi: Kiểu giá trị nullable (nullable value type) là gì? Kiểu giá trị nullable T? là struct System.Nullable<T> có thể giữ mọi giá trị của T cộng thêm null. Nó cung cấp thuộc tính .HasValue (bool) và .Value (T). Hữu ích cho việc đại diện giá trị tùy chọn hoặc giá trị cơ sở dữ liệu.

Câu hỏi: Kiểu tham chiếu nullable (nullable reference type) là gì? Kiểu tham chiếu nullable (C# 8+) là tính năng phân tích thời điểm biên dịch. Khi bật, string? có nghĩa là "tham chiếu này có thể là null" và string có nghĩa là "tham chiếu này không nên là null." Trình biên dịch cảnh báo khi tham chiếu nullable được giải tham chiếu mà không có kiểm tra null. Đây không phải thay đổi thời điểm chạy — nó chỉ tạo cảnh báo.

Câu hỏi: Sự khác biệt giữa intint? là gì? int là kiểu giá trị lưu trên stack không thể null. int?Nullable<int>, một struct bao bọc int với cờ HasValue, cho phép đại diện null. int? có dấu chân bộ nhớ lớn hơn và yêu cầu .Value hoặc .GetValueOrDefault() để truy cập giá trị cơ sở.

Câu hỏi: Toán tử ?. làm gì? Toán tử điều kiện null (null-conditional operator) (?.) ngắt mạch truy cập thành viên nếu toán hạng là null. Nếu obj là null, obj?.Property trả về null thay vì ném NullReferenceException. Kiểu trả về luôn là nullable. Kết hợp với ?? để cung cấp giá trị mặc định: obj?.Property ?? fallback.

Câu hỏi: Sự khác biệt giữa kiểu giá trị nullable và kiểu tham chiếu nullable là gì? Kiểu giá trị nullable (int?) là tính năng thời điểm chạy — chúng bao bọc giá trị trong Nullable<T> với trạng thái bổ sung. Kiểu tham chiếu nullable (string?) là tính năng chỉ thời điểm biên dịch — trình biên dịch phân tích trạng thái null và tạo cảnh báo, nhưng biểu diễn thời điểm chạy giống như tham chiếu thông thường.

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