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

Modern C# Features

Định nghĩa (Definition)

C# hiện đại (Modern C#) đề cập đến các tính năng được giới thiệu từ C# 8 trở đi giúp cải thiện tính biểu đạt, độ an toàn và hiệu suất. Mỗi phiên bản mang lại các cải tiến gia tăng giúp giảm mã soạn sẵn (Boilerplate), cho phép các mẫu mới và đưa C# gần hơn với ngôn ngữ "nghi thức thấp" (Low-ceremony) trong khi vẫn duy trì khả năng tương thích ngược.

Tính năng C# 8 (C# 8 Features)

Kiểu tham chiếu có thể null (Nullable Reference Types)

Xem Nullable Types để biết đầy đủ. Cho phép an toàn null tại thời điểm biên dịch:

#nullable enable
string name = "Alice"; // Non-nullable
string? nick = null; // Nullable

Biểu thức Switch (Switch Expressions)

Xem Pattern Matching. Biểu thức switch dựa trên biểu thức với khớp mẫu (Pattern Matching):

string Grade(int score) => score switch
{
>= 90 => "A",
>= 80 => "B",
_ => "F"
};

Luồng Không đồng bộ (IAsyncEnumerable<T>) (Async Streams)

Lặp không đồng bộ (Asynchronous Iteration) — trả về các mục khi chúng sẵn sàng:

async IAsyncEnumerable<string> ReadLinesAsync(string path)
{
using var reader = new StreamReader(path);
while (await reader.ReadLineAsync() is { } line)
{
yield return line;
}
}

// Consume with await foreach
await foreach (var line in ReadLinesAsync("data.csv"))
{
Console.WriteLine(line);
}

Chỉ mục và Phạm vi (Indices and Ranges)

Cú pháp mới cho lập chỉ mục và cắt mảng:

int[] numbers = { 0, 1, 2, 3, 4, 5 };

int last = numbers[^1]; // 5 — from end
int[] slice = numbers[1..4]; // [1, 2, 3] — exclusive end
int[] lastTwo = numbers[^2..]; // [4, 5]
int[] all = numbers[..]; // copy of entire array

Khai báo Using (Using Declarations)

Không còn khối lồng nhau cho việc giải phóng:

using var connection = new SqlConnection(connectionString);
using var command = connection.CreateCommand();
// Dispose called automatically at end of scope

Thành viên Giao diện Mặc định (Default Interface Members)

Giao diện có thể định nghĩa triển khai phương thức:

public interface ILogger
{
void Log(string message);

void LogWarning(string message) => Log($"[WARN] {message}");
void LogError(string message) => Log($"[ERROR] {message}");
}

Tính năng C# 9 (C# 9 Features)

Bản ghi (Records)

Kiểu tham chiếu bất biến (Immutable Reference Types) với so sánh dựa trên giá trị (Value-based Equality):

public record Person(string FirstName, string LastName, int Age);

var p1 = new Person("Alice", "Smith", 30);
var p2 = new Person("Alice", "Smith", 30);

Console.WriteLine(p1 == p2); // True — value equality
var p3 = p1 with { Age = 31 }; // Non-destructive mutation

Bộ đặt Chỉ khởi tạo (Init-Only Setters)

Thuộc tính chỉ có thể được đặt trong quá trình khởi tạo đối tượng:

public class Configuration
{
public string Host { get; init; } = "localhost";
public int Port { get; init; } = 5432;
}

var config = new Configuration { Host = "db.example.com", Port = 3306 };
// config.Host = "other"; // Compile error — init-only

Câu lệnh Cấp cao nhất (Top-Level Statements)

Giảm mã soạn sẵn cho các chương trình đơn giản:

// Program.cs — the entire file
using Microsoft.AspNetCore.Builder;

var app = WebApplication.CreateBuilder().Build();
app.MapGet("/", () => "Hello, World!");
app.Run();

new Theo kiểu Đích (Target-Typed New)

Để trình biên dịch suy luận kiểu từ ngữ cảnh:

Person person = new("Alice", "Smith", 30);
List<int> numbers = new() { 1, 2, 3 };
Dictionary<string, int> map = new() { ["a"] = 1 };

Kiểu trả về Hiệp biến (Covariant Return Types)

Ghi đè phương thức với kiểu trả về cụ thể hơn:

public abstract class Repository
{
public abstract Entity GetById(int id);
}

public class SqlRepository : Repository
{
public override SqlEntity GetById(int id) => // More specific return type
_context.SqlEntities.Find(id);
}

Tính năng C# 10 (C# 10 Features)

using Toàn cục (Global Usings)

Định nghĩa using một lần cho toàn bộ dự án:

// GlobalUsings.cs
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading.Tasks;
<!-- Or auto-generate in .csproj -->
<ImplicitUsings>enable</ImplicitUsings>

Không gian tên Phạm vi Tệp (File-Scoped Namespaces)

Giảm thụt lề — một trong những cải tiến chất lượng cuộc sống có tác động nhất:

namespace MyApp.Services; // No braces needed

public class UserService
{
// All types in this file belong to MyApp.Services
}

Bản ghi Struct (Record Structs)

Kiểu bản ghi giá trị (Value-type Records) với so sánh dựa trên giá trị:

public record struct Point(double X, double Y);

var a = new Point(1, 2);
var b = new Point(1, 2);
Console.WriteLine(a == b); // True

Chuỗi Nội suy Hằng số (Constant Interpolated Strings)

const string Prefix = "ERROR_";
const string ErrorCode = $"{Prefix}NOT_FOUND"; // Compile-time constant

Mẫu Thuộc tính Mở rộng (Extended Property Patterns)

Khớp thuộc tính lồng nhau đơn giản hóa:

// C# 9 — nested braces required
if (order is { Customer: { IsVip: true } }) { }

// C# 10 — dot notation
if (order is { Customer.IsVip: true }) { }

Tính năng C# 11 (C# 11 Features)

Chuỗi ký tự Thô (Raw String Literals)

Chuỗi nhiều dòng không cần thoát ký tự — lý tưởng cho JSON, SQL, regex:

var json = """
{
"name": "Alice",
"age": 30,
"regex": "C:\\Users\\.*"
}
""";

var query = """
SELECT u.Id, u.Name
FROM Users u
WHERE u.Active = 1
""";

Mẫu Danh sách (List Patterns)

Khớp các phần tử trong mảng và danh sách:

int[] numbers = { 1, 2, 3, 4, 5 };
var message = numbers switch
{
[] => "Empty",
[var single] => $"One: {single}",
[var a, var b] => $"Two: {a}, {b}",
[var first, .., var last] => $"First: {first}, Last: {last}",
};

Thành viên Bắt buộc (Required Members)

Buộc người gọi phải khởi tạo các thuộc tính cụ thể:

public class User
{
public required string Email { get; init; }
public required string Name { get; init; }
public string? Phone { get; init; }
}

var user = new User { Email = "a@b.com", Name = "Alice" }; // OK
// var bad = new User { Name = "Alice" }; // Error — Email is required

Hỗ trợ Toán học Chung (Generic Math Support)

Sử dụng toán tử số học trong mã chung (Generic Code):

T Add<T>(T a, T b) where T : INumber<T> => a + b;

T Sum<T>(params T[] values) where T : INumber<T> =>
values.Aggregate(T.Zero, (acc, v) => acc + v);

Struct Tự động Mặc định (Auto-Default Structs)

Các trường trong struct được tự động khởi tạo với giá trị mặc định:

public struct Measurement
{
public double Value; // Auto-initialized to 0.0
public string Unit; // Auto-initialized to null
}

Kiểu Phạm vi Tệp (File-Scoped Types)

Các kiểu chỉ hiển thị trong tệp nơi chúng được khai báo:

file class Helper { /* Only visible in this file */ }

Tính năng C# 12 (C# 12 Features)

Constructor Chính cho Lớp (Primary Constructors for Classes)

Các tham số được khai báo trên chính lớp — có sẵn trong toàn bộ lớp:

public class ProductService(IRepository<Product> repo, ILogger<ProductService> logger)
{
public Product? Find(int id)
{
logger.LogInformation("Finding product {Id}", id);
return repo.Find(id);
}
}
Tham số constructor chính không phải là thuộc tính

Khác với bản ghi (Record), tham số constructor chính trong lớp không tự động được hiển thị như thuộc tính. Chúng được ghi nhận vào thân lớp. Nếu bạn cần thuộc tính công khai, hãy khai báo rõ ràng:

public class UserController(ILogger logger)
{
public ILogger Logger { get; } = logger; // Explicit property
}

Biểu thức Bộ sưu tập (Collection Expressions)

Cú pháp thống nhất để khởi tạo bộ sưu tập:

int[] array = [1, 2, 3];
List<string> list = ["a", "b", "c"];
Span<int> span = [10, 20, 30];
ReadOnlySpan<char> ros = ['h', 'e', 'l', 'l', 'o'];

// Spread elements from another collection
int[] all = [..array, 4, 5]; // [1, 2, 3, 4, 5]

Mảng Nội tuyến (Inline Arrays)

Mảng kích thước cố định hiệu suất cao mà không cần phân bổ heap:

[System.Runtime.CompilerServices.InlineArray(4)]
public struct FourInts
{
private int _element0;
}

var buffer = new FourInts();
buffer[0] = 10;
buffer[1] = 20;

Bí danh cho Bất kỳ Kiểu nào (Alias Any Type)

Sử dụng bí danh using cho bất kỳ kiểu nào, bao gồm tuple và kiểu có thể null:

using Point = (double X, double Y);
using UserId = int?;

Tính năng C# 13 (C# 13 Features)

Bộ sưu tập Params (Params Collections)

params giờ hoạt động với ReadOnlySpan<T>List<T>, không chỉ mảng:

void Log(ReadOnlySpan<string> messages)
{
foreach (var msg in messages)
Console.WriteLine(msg);
}

Log("Starting", "Processing", "Done"); // No array allocation

Đối tượng Lock Mới (New Lock Object)

System.Threading.Lock thay thế lock(obj) với hiệu suất tốt hơn:

private readonly Lock _lock = new();

using (_lock.EnterScope())
{
// Critical section — faster than monitor-based lock
}

Thuộc tính Một phần (Partial Properties)

Tách khai báo khỏi triển khai (hữu ích cho trình tạo mã nguồn - Source Generators):

public partial string ConnectionString { get; }

// In generated file:
public partial string ConnectionString => _config.GetConnectionString();

Từ khóa field (Field Keyword)

Truy cập trường sao lưu (Backing Field) từ bộ truy cập thuộc tính mà không cần khai báo rõ ràng:

public string Name
{
get => field;
set => field = value?.Trim() ?? throw new ArgumentNullException();
}

Kiểu Mở rộng (Preview) (Extension Types)

Cách tiếp cận mới để mở rộng kiểu mà không cần sửa đổi chúng:

extension StringExtensions for string
{
public bool IsNullOrEmpty => string.IsNullOrEmpty(this);
public string Capitalize => char.ToUpper(this[0]) + this[1..];
}

Hướng dẫn Chuyển đổi (Migration Guidance)

Áp dụng các tính năng hiện đại một cách gia tăng:

  1. Rủi ro thấp, tác động cao trước — không gian tên phạm vi tệp, using toàn cục, new theo kiểu đích, khai báo using
  2. Thêm vào mã mới — bản ghi (Records), bộ đặt chỉ khởi tạo, biểu thức switch, khớp mẫu (Pattern Matching)
  3. Kích hoạt theo tệp — kiểu tham chiếu có thể null (#nullable enable), constructor chính
  4. Thay đổi toàn dự án cuối cùngImplicitUsings, Nullable trong .csproj
<!-- Recommended .csproj settings for modern C# -->
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

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

Lạm dụng câu lệnh cấp cao nhất (Top-level Statements)

Câu lệnh cấp cao nhất tuyệt vời cho các chương trình đơn giản và API tối thiểu, nhưng có thể làm cho ứng dụng lớn khó điều hướng hơn. Khi Program.cs phát triển quá vài chục dòng, hãy trích xuất thành các lớp và phương thức.

Cảnh báo Nullable trong mã kế thừa

Kích hoạt <Nullable>enable</Nullable> toàn dự án trên mã kế thừa tạo ra hàng trăm cảnh báo. Di chuyển từng tệp với #nullable enable và sửa cảnh báo trước khi đặt ở cấp dự án. Không đặt <TreatWarningsAsErrors> cho cảnh báo nullable cho đến khi di chuyển hoàn tất.

Bản ghi (Record) vs Lớp (Class)

Sử dụng record cho các kiểu hướng dữ liệu nơi so sánh dựa trên giá trị phù hợp (DTO, đối tượng giá trị, sự kiện). Sử dụng class cho các kiểu với so sánh dựa trên danh tính (thực thể, dịch vụ). Sử dụng record struct cho các kiểu giá trị nhỏ.

Điểm chính (Key Takeaways)

  1. Mỗi phiên bản C# thêm các tính năng giảm mã soạn sẵn (Boilerplate) và cải thiện độ an toàn — áp dụng gia tăng.
  2. Bản ghi (Records) cung cấp các kiểu dữ liệu bất biến, so sánh theo giá trị với cú pháp tối thiểu.
  3. Không gian tên phạm vi tệp và using toàn cục giảm đáng kể nhiễu trong mỗi tệp.
  4. Kiểu tham chiếu có thể null bắt lỗi null tại thời điểm biên dịch — hãy kích hoạt chúng.
  5. Khớp mẫu (Pattern Matching) và biểu thức switch thay thế phân nhánh dài dòng.
  6. Constructor chính (C# 12) đơn giản hóa tiêm phụ thuộc (Dependency Injection) trong dịch vụ.
  7. Sử dụng LangVersion đặt thành latest để luôn có quyền truy cập vào các tính năng mới.

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

Q: Có gì mới trong C# 10? C# 10 giới thiệu using toàn cục, không gian tên phạm vi tệp, bản ghi struct, chuỗi nội suy hằng số, mẫu thuộc tính mở rộng và cải thiện suy luận kiểu lambda. Đây chủ yếu là các cải tiến chất lượng cuộc sống giúp giảm mã soạn sẵn.

Q: Có gì mới trong C# 11? C# 11 thêm chuỗi ký tự thô, mẫu danh sách, thành viên bắt buộc, hỗ trợ toán học chung (INumber<T>), kiểu phạm vi tệp và struct tự động mặc định. Chuỗi ký tự thô và thành viên bắt buộc có tác động hàng ngày lớn nhất.

Q: Có gì mới trong C# 12? C# 12 mang đến constructor chính cho lớp, biểu thức bộ sưu tập ([1, 2, 3]), mảng nội tuyến, bí danh cho bất kỳ kiểu nào và params tùy chọn với ReadOnlySpan. Constructor chính và biểu thức bộ sưu tập được áp dụng rộng rãi nhất.

Q: Bản ghi (Records) là gì? Bản ghi là kiểu tham chiếu (hoặc kiểu struct với record struct) cung cấp so sánh dựa trên giá trị, đột biến không phá hủy (biểu thức with) và định dạng tích hợp sẵn. Chúng lý tưởng cho DTO, đối tượng giá trị và kiểu sự kiện.

Q: Câu lệnh cấp cao nhất (Top-level Statements) là gì? Câu lệnh cấp cao nhất cho phép bạn viết chương trình C# mà không cần lớp Program hay phương thức Main. Trình biên dịch tự động tạo phương thức Main. Chúng lý tưởng cho API tối thiểu và chương trình console đơn giản.

Q: Sự khác biệt giữa initset là gì? init cho phép thuộc tính chỉ được đặt trong quá trình khởi tạo đối tượng (constructor hoặc bộ khởi tạo đối tượng). Sau khi khởi tạo, thuộc tính thực chất là chỉ đọc. set cho phép thuộc tính được thay đổi bất kỳ lúc nào. Sử dụng init cho các đối tượng cấu hình bất biến và DTO.

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