Mục tiêu: Master các tính năng C# mới nhất, viết code expressive, performant, và type-safe hơn.
1. Pattern Matching Nâng Cao
Property Patterns (C# 8+)
csharp// Nested property pattern
public decimal CalculateShipping(Order order) => order switch
{
{ Total: > 500, Customer.IsPremium: true } => 0m,
{ Total: > 500 } => 9.99m,
{ Weight: > 20, Destination.Country: "VN" } => 25m,
{ Weight: > 20 } => 50m,
{ Total: > 100 } => 15m,
_ => 30m
};
List Patterns (C# 11)
csharp// Match trên collections
public string AnalyzeRoute(int[] segments) => segments switch
{
[] => "Empty route",
[var single] => $"Direct: {single}",
[var first, .., var last] => $"Route from {first} to {last}",
};
// Real-world: parse CSV header
public bool IsValidCsvHeader(string[] headers) => headers switch
{
["Id", "Name", "Price", ..] => true, // bắt đầu đúng
[_, _, _, .., "Total"] => true, // kết thúc bằng Total
{ Length: > 100 } => false, // quá nhiều columns
_ => false
};
// Relational + Logical patterns
public string ClassifyTemperature(double temp) => temp switch
{
< 0 => "Freezing",
>= 0 and < 15 => "Cold",
>= 15 and < 30 => "Comfortable",
>= 30 and < 40 => "Hot",
>= 40 => "Extreme",
double.NaN => "Invalid"
};
Switch Expression với Tuples
csharp// Multi-value pattern matching
public decimal CalculateTax(string country, string productType, decimal amount) =>
(country, productType) switch
{
("VN", "food") => amount * 0.05m,
("VN", "electronics") => amount * 0.10m,
("VN", _) => amount * 0.08m,
("US", "food") => 0m,
("US", _) => amount * 0.07m,
(_, "luxury") => amount * 0.20m,
_ => amount * 0.10m
};
2. Records & Value Objects
Record Types (C# 9+)
csharp// Record class - reference type, immutable by convention
public record OrderCreatedEvent(
Guid OrderId,
string CustomerId,
decimal Total,
DateTime CreatedAt
);
// Non-destructive mutation với "with"
var original = new OrderCreatedEvent(Guid.NewGuid(), "C001", 100m, DateTime.UtcNow);
var modified = original with { Total = 150m };
// original.Total vẫn = 100m
// Record struct (C# 10) - value type, stack-allocated
public readonly record struct Money(decimal Amount, string Currency)
{
// Tự có: Value equality, ToString(), Deconstruct()
public static Money operator +(Money a, Money b)
{
if (a.Currency != b.Currency)
throw new InvalidOperationException("Currency mismatch");
return a with { Amount = a.Amount + b.Amount };
}
}
// DDD Value Object pattern với record
public record Address
{
public string Street { get; init; } = null!;
public string City { get; init; } = null!;
public string PostalCode { get; init; } = null!;
// Custom validation trong constructor
public Address
{
ArgumentException.ThrowIfNullOrWhiteSpace(Street);
ArgumentException.ThrowIfNullOrWhiteSpace(City);
}
}
Record vs Class vs Struct - Khi Nào Chọn Gì?
| Tính năng | class | record class | record struct | struct |
|---|---|---|---|---|
| Allocation | Heap | Heap | Stack | Stack |
| Equality | Reference | Value | Value | Value (nếu override) |
| Mutable | Có | Có thể | Có thể | Có |
| Inheritance | Có | Record inheritance | Không | Không |
with expression | Không | Có | Có | Không |
| Use case | Domain entities | Events, DTOs | Small value types | Performance-critical |
3. Primary Constructors & Required Members
Primary Constructors (C# 12)
csharp// Class/Struct với Primary Constructor
public class OrderService(
IOrderRepository repository,
ILogger<OrderService> logger,
TimeProvider timeProvider)
{
// Parameters captured as private fields tự động
// KHÔNG tạo public properties - chúng là captured variables
public async Task<Order> CreateOrder(CreateOrderCommand cmd)
{
logger.LogInformation("Creating order for {CustomerId}", cmd.CustomerId);
var order = Order.Create(cmd, timeProvider.GetUtcNow());
await repository.AddAsync(order);
return order;
}
}
// ⚠️ Gotcha: Primary constructor params KHÔNG phải readonly
// Chúng có thể bị reassign bên trong class!
public class Counter(int initial)
{
public int Current => initial;
public void Increment() => initial++; // ⚠️ Mutate captured variable!
}
Required Members (C# 11)
csharppublic class CreateOrderRequest
{
public required string CustomerId { get; init; }
public required List<OrderItem> Items { get; init; }
public string? CouponCode { get; init; } // optional
}
// Compile error nếu thiếu required properties:
// ❌ var req = new CreateOrderRequest();
// ✅ var req = new CreateOrderRequest
// {
// CustomerId = "C001",
// Items = [new OrderItem("SKU-1", 2)]
// };
// Kết hợp với SetsRequiredMembers
public class Product
{
public required string Name { get; init; }
public required decimal Price { get; init; }
[SetsRequiredMembers]
public Product(string name, decimal price)
{
Name = name;
Price = price;
}
}
4. Source Generators
Tại Sao Source Generators?
Reflection (runtime):
Code → IL → JIT → Runtime inspect types → Slow, not AOT-friendly
Source Generators (compile-time):
Code → Roslyn Analyzer → Generated C# code → IL → Fast, AOT-compatible
Các Source Generators Quan Trọng Trong .NET
csharp// 1. System.Text.Json Source Generator
[JsonSerializable(typeof(List<Product>))]
[JsonSerializable(typeof(OrderResponse))]
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
public partial class AppJsonContext : JsonSerializerContext { }
// Sử dụng:
app.MapGet("/products", () =>
TypedResults.Ok(products))
.WithOpenApi();
// Trong Program.cs cho Minimal API:
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});
// 2. Logging Source Generator (high-performance logging)
public static partial class LogMessages
{
[LoggerMessage(Level = LogLevel.Information,
Message = "Processing order {OrderId} for customer {CustomerId}")]
public static partial void OrderProcessing(
this ILogger logger, Guid orderId, string customerId);
[LoggerMessage(Level = LogLevel.Error,
Message = "Payment failed for order {OrderId}: {Reason}")]
public static partial void PaymentFailed(
this ILogger logger, Guid orderId, string reason, Exception ex);
}
// Sử dụng: logger.OrderProcessing(order.Id, order.CustomerId);
// → Zero-allocation logging, compile-time message validation
// 3. Regex Source Generator
public partial class Validators
{
[GeneratedRegex(@"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")]
private static partial Regex EmailRegex();
[GeneratedRegex(@"^\+?[1-9]\d{1,14}$")]
private static partial Regex PhoneRegex();
public static bool IsValidEmail(string email) => EmailRegex().IsMatch(email);
}
// → Compiled tại build time, ~5x nhanh hơn Regex.IsMatch()
Viết Custom Source Generator
csharp// Ví dụ: Auto-generate interface từ class
[Generator]
public class AutoInterfaceGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 1. Tìm tất cả classes có [AutoInterface] attribute
var classDeclarations = context.SyntaxProvider
.ForAttributeWithMetadataName(
"AutoInterfaceAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => GetClassInfo(ctx))
.Where(x => x is not null);
// 2. Generate interface source code
context.RegisterSourceOutput(classDeclarations, (spc, classInfo) =>
{
var source = GenerateInterface(classInfo!);
spc.AddSource($"I{classInfo!.Name}.g.cs", source);
});
}
}
5. Span<T>, Memory<T> & ref struct
Span<T> - Zero-Copy Slicing
csharp// Span<T> là "view" vào memory, KHÔNG copy data
public static (ReadOnlySpan<char> scheme, ReadOnlySpan<char> host, ReadOnlySpan<char> path)
ParseUrl(ReadOnlySpan<char> url)
{
// "https://api.example.com/orders/123"
int schemeEnd = url.IndexOf("://");
var scheme = url[..schemeEnd]; // "https" - zero copy!
var afterScheme = url[(schemeEnd + 3)..];
int pathStart = afterScheme.IndexOf('/');
var host = afterScheme[..pathStart]; // "api.example.com"
var path = afterScheme[pathStart..]; // "/orders/123"
return (scheme, host, path);
}
// So sánh với String: mỗi Substring() tạo string mới trên heap
// Span: zero allocation, chỉ là con trỏ + length
// stackalloc kết hợp Span
public static string FormatGuid(Guid id)
{
Span<char> buffer = stackalloc char[36];
id.TryFormat(buffer, out _);
return new string(buffer);
}
Memory<T> - Async-Compatible Span
csharp// Span<T> là ref struct → KHÔNG thể dùng trong async methods
// Memory<T> là wrapper cho phép truyền qua async boundary
public async Task ProcessDataAsync(Memory<byte> data)
{
// Có thể truyền Memory<T> qua async
await ProcessChunkAsync(data[..1024]);
await ProcessChunkAsync(data[1024..]);
}
private async Task ProcessChunkAsync(Memory<byte> chunk)
{
// Khi cần read, tạo Span từ Memory
var span = chunk.Span;
// Xử lý đồng bộ với span...
}
ref struct & Scoped
csharp// ref struct: chỉ sống trên stack, KHÔNG thể escape lên heap
// Ví dụ: Span<T>, ReadOnlySpan<T>, Utf8JsonReader
ref struct ValueStringBuilder
{
private Span<char> _buffer;
private int _length;
public ValueStringBuilder(Span<char> buffer)
{
_buffer = buffer;
_length = 0;
}
public void Append(ReadOnlySpan<char> value)
{
value.CopyTo(_buffer[_length..]);
_length += value.Length;
}
public override string ToString() => _buffer[.._length].ToString();
}
// Sử dụng: zero-allocation string building
Span<char> buffer = stackalloc char[256];
var sb = new ValueStringBuilder(buffer);
sb.Append("Hello ");
sb.Append("World");
var result = sb.ToString(); // Chỉ 1 allocation (final string)
6. Collection Expressions & Params Collections (C# 12/13)
csharp// Collection Expressions (C# 12)
// Unified syntax cho mọi collection type
int[] numbers = [1, 2, 3, 4, 5];
List<string> names = ["Alice", "Bob", "Charlie"];
Span<int> span = [10, 20, 30];
ImmutableArray<int> immutable = [1, 2, 3];
// Spread operator
int[] first = [1, 2, 3];
int[] second = [4, 5, 6];
int[] combined = [..first, ..second]; // [1, 2, 3, 4, 5, 6]
// Trong method return
public IReadOnlyList<string> GetDefaultRoles() => ["User", "Viewer"];
// Params collections (C# 13 / .NET 9)
// params hỗ trợ mọi collection type, không chỉ arrays
public void Log(params ReadOnlySpan<string> messages)
{
foreach (var msg in messages)
Console.WriteLine(msg);
}
// Gọi: Log("Hello", "World"); // zero allocation!
// Trước: params string[] → luôn allocate array
// Real-world: builder pattern
public QueryBuilder Where(params ReadOnlySpan<string> conditions)
{
foreach (var condition in conditions)
_conditions.Add(condition);
return this;
}
var query = new QueryBuilder()
.Where("status = 'active'", "price > 100", "category = 'electronics'");
7. Interceptors (C# 12 Preview)
csharp// Interceptors cho phép Source Generators "thay thế" method calls
// tại compile time → tối ưu performance
// Original code:
app.MapGet("/products", () => db.Products.ToListAsync());
// Source Generator có thể intercept và thay bằng optimized version:
// [InterceptsLocation("Program.cs", line: 5, column: 5)]
// public static RouteHandlerBuilder MapGet_Optimized(...)
// ⚠️ Đây là feature experimental, chủ yếu dùng bởi framework authors
// (ASP.NET Core dùng để optimize Minimal API route handlers)
8. Tổng Kết & Best Practices
| Feature | Khi nào dùng | Khi nào KHÔNG dùng |
|---|---|---|
| Pattern Matching | Complex conditionals, type checks | Đơn giản if/else đủ |
| Records | DTOs, Events, Value Objects | Entities cần mutable state |
| Primary Constructors | DI injection, simple constructors | Cần complex init logic |
| Source Generators | High-perf serialization, logging | Simple one-off tasks |
| Span<T> | Hot paths, parsing, zero-copy | Async methods (dùng Memory<T>) |
| Collection Expressions | Mọi nơi tạo collection literal | - |
Bài tập: Refactor 1 service class trong project của bạn: dùng Primary Constructor cho DI,
[LoggerMessage]cho logging, và pattern matching cho business logic. Đo performance trước/sau bằng BenchmarkDotNet.