Mục tiêu: Hiểu sâu kiến trúc nội bộ .NET, sự khác biệt giữa các phiên bản, và cách runtime hoạt động ở mức thấp.
1. .NET Framework vs .NET Core vs .NET 5-9: Bản Chất Khác Biệt
Timeline Evolution
.NET Framework 1.0 (2002) → 4.8.1 (cuối cùng, chỉ còn security patches)
↓
.NET Core 1.0 (2016) → 3.1 (LTS)
↓
.NET 5 (2020) → Unified platform
↓
.NET 6 (LTS) → .NET 7 → .NET 8 (LTS) → .NET 9 (STS, Nov 2024)
So Sánh Kiến Trúc Nội Bộ
| Tiêu chí | .NET Framework | .NET (Core/5+) |
|---|---|---|
| Runtime | CLR (Common Language Runtime) | CoreCLR (cross-platform) |
| Base Class Library | BCL (monolithic) | CoreFX → System.* (modular NuGet) |
| Hosting | IIS-only (System.Web) | Kestrel + reverse proxy |
| Deployment | Machine-wide install | Self-contained / Framework-dependent |
| Config | web.config (XML) | appsettings.json + Environment |
| DI | Không built-in | Built-in DI container |
| Side-by-side | Không (machine-wide) | Có (mỗi app riêng runtime) |
| Performance | ~50K req/s | ~500K+ req/s (TechEmpower) |
| AOT | NGen (limited) | Native AOT (.NET 7+) |
Khi Nào Còn Dùng .NET Framework?
✅ Legacy apps đang chạy ổn định, không cần migrate
✅ Dùng WCF (server-side), COM+, .NET Remoting
✅ Windows-only apps dùng Windows Forms, WPF
✅ Dự án phụ thuộc library chỉ support .NET Framework
❌ Mọi dự án MỚI → luôn dùng .NET 8/9
Migration Strategy: Framework → .NET 8/9
Step 1: .NET Portability Analyzer → check compatibility
Step 2: Upgrade NuGet packages → tìm .NET Standard 2.0 alternatives
Step 3: Chuyển từ System.Web → ASP.NET Core middleware
Step 4: web.config → appsettings.json + Program.cs
Step 5: Global.asax → Middleware pipeline
2. CLR & CoreCLR Internals
Kiến Trúc Runtime
┌──────────────────────────────────────────────┐
│ Your C# Code │
├──────────────────────────────────────────────┤
│ IL (Intermediate Language) │
├──────────────────────────────────────────────┤
│ CoreCLR / Runtime │
│ ┌─────────┬──────────┬───────────────────┐ │
│ │ JIT │ GC │ Type System │ │
│ │Compiler │ Manager │ (Metadata) │ │
│ ├─────────┼──────────┼───────────────────┤ │
│ │ Thread │Exception │ Interop │ │
│ │ Pool │ Handling │ (P/Invoke, COM) │ │
│ └─────────┴──────────┴───────────────────┘ │
├──────────────────────────────────────────────┤
│ OS Abstraction Layer (PAL) │
├──────────────────────────────────────────────┤
│ Windows / Linux / macOS │
└──────────────────────────────────────────────┘
Assembly Loading
csharp// .NET Framework: GAC + probing paths
// .NET Core/5+: AssemblyLoadContext (ALC)
// Default ALC - cho app chính
var defaultContext = AssemblyLoadContext.Default;
// Custom ALC - cho plugin isolation
public class PluginLoadContext : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver _resolver;
public PluginLoadContext(string pluginPath) : base(isCollectible: true)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
}
protected override Assembly? Load(AssemblyName assemblyName)
{
// Ưu tiên resolve từ plugin directory
string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
return assemblyPath != null
? LoadFromAssemblyPath(assemblyPath)
: null; // fallback về Default ALC
}
}
// Sử dụng: load plugin động, unload khi không cần
var context = new PluginLoadContext("plugins/MyPlugin.dll");
var assembly = context.LoadFromAssemblyPath(pluginPath);
// ... dùng xong
context.Unload(); // GC sẽ thu hồi memory
Key insight:
isCollectible: truecho phép unload assembly - cực kỳ quan trọng cho plugin systems và hot-reload.
3. JIT Compilation Deep Dive
Tiered Compilation (Mặc Định Từ .NET Core 3.0+)
┌───────────────────┐
Method gọi │ Tier 0 (Quick) │ ← Compile nhanh, chưa optimize
lần đầu ────────→│ Minimal inlining │ để app start nhanh
│ No loop optimize │
└────────┬──────────┘
│
Gọi > 30 lần (threshold)
│
┌────────▼──────────┐
│ Tier 1 (Full) │ ← Re-compile với full optimization
│ Inlining, CSE │ background thread
│ Loop unrolling │
│ Dead code elim │
└───────────────────┘
csharp// Kiểm soát Tiered Compilation
// Trong .csproj
<PropertyGroup>
<TieredCompilation>true</TieredCompilation> <!-- default: true -->
<TieredPGO>true</TieredPGO> <!-- Profile-Guided Opt, .NET 8+ default -->
</PropertyGroup>
// Hoặc environment variable
// DOTNET_TieredCompilation=1
// DOTNET_TieredPGO=1
ReadyToRun (R2R) - Ahead-of-Time Pre-compilation
xml<!-- Trong .csproj: publish với R2R -->
<PropertyGroup>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
R2R Assembly chứa:
├── IL Code (vẫn giữ nguyên)
├── R2R Native Code (pre-compiled cho target OS/arch)
└── Runtime vẫn có thể re-JIT (Tier 1) nếu cần optimize thêm
→ Startup nhanh hơn ~30-50%, binary size lớn hơn ~2x
→ Phù hợp: Azure Functions, serverless, CLI tools
Native AOT (.NET 7+)
xml<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
Native AOT:
✅ Startup cực nhanh (~10ms), memory thấp
✅ Single file, không cần runtime installed
✅ Phù hợp: microservices, containers, IoT, CLI
❌ Không hỗ trợ: dynamic loading, Reflection.Emit, COM interop
❌ Cần trim analysis: annotate code với [DynamicallyAccessedMembers]
❌ Binary size lớn nếu không trim kỹ
csharp// AOT-compatible code: dùng Source Generators thay Reflection
// ❌ Reflection (AOT-incompatible)
var json = JsonSerializer.Serialize(obj);
// ✅ Source Generator (AOT-compatible)
[JsonSerializable(typeof(MyClass))]
public partial class MyJsonContext : JsonSerializerContext { }
var json = JsonSerializer.Serialize(obj, MyJsonContext.Default.MyClass);
So Sánh Compilation Strategies
| Strategy | Startup | Throughput | Binary Size | Compatibility |
|---|---|---|---|---|
| JIT (default) | Chậm | Tốt nhất (PGO) | Nhỏ | 100% |
| R2R | Nhanh hơn 50% | Tốt | 2x | 100% |
| Native AOT | Cực nhanh | Tốt | 1 file lớn | ~85% |
4. Garbage Collector Internals
GC Generations
┌─────────────────────────────────────────────────────┐
│ Managed Heap │
├──────────┬──────────┬───────────────────────────────┤
│ Gen 0 │ Gen 1 │ Gen 2 │
│ ~256KB │ ~2MB │ Growing... │
│ (young) │(survivor)│ (long-lived) │
├──────────┴──────────┴───────────────────────────────┤
│ LOH (Large Object Heap) │
│ Objects >= 85,000 bytes │
│ Collected với Gen 2 │
├─────────────────────────────────────────────────────┤
│ POH (Pinned Object Heap) - .NET 5+ │
│ GC.AllocateArray<byte>(size, pinned: true) │
│ Tránh fragmentation do pinning │
└─────────────────────────────────────────────────────┘
GC Modes
csharp// Workstation GC (default cho desktop apps)
// - 1 GC thread
// - Lower latency, lower throughput
// Server GC (default cho ASP.NET Core)
// - 1 GC thread per logical core
// - Higher throughput, higher memory
// Cấu hình trong .csproj:
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
</PropertyGroup>
// .NET 8+: DATAS (Dynamic Adaptation To Application Sizes)
// Tự động điều chỉnh giữa Server/Workstation dựa trên workload
<PropertyGroup>
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
</PropertyGroup>
GC Tuning Practices
csharp// 1. Tránh allocation cho hot paths
// ❌ Bad: allocate mỗi request
public string FormatPrice(decimal price) => $"${price:F2}";
// ✅ Good: dùng Span, stackalloc
public bool TryFormatPrice(decimal price, Span<char> destination, out int written)
{
return price.TryFormat(destination, out written, "$#.00");
}
// 2. Object Pooling cho objects tốn kém
private static readonly ObjectPool<StringBuilder> _sbPool =
new DefaultObjectPoolProvider().CreateStringBuilderPool();
public string BuildReport(IEnumerable<Item> items)
{
var sb = _sbPool.Get();
try
{
foreach (var item in items)
sb.AppendLine($"{item.Name}: {item.Price:C}");
return sb.ToString();
}
finally
{
_sbPool.Return(sb);
}
}
// 3. ArrayPool cho large arrays
public async Task ProcessFileAsync(Stream stream)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(81920);
try
{
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer)) > 0)
{
ProcessChunk(buffer.AsSpan(0, bytesRead));
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer, clearArray: true);
}
}
// 4. Pinned Object Heap (.NET 5+) - tránh fragmentation
byte[] pinnedBuffer = GC.AllocateArray<byte>(4096, pinned: true);
// Dùng cho P/Invoke, I/O buffers cần pin lâu dài
Finalization & IDisposable Pattern
csharp// Modern dispose pattern (.NET 5+ recommended)
public class DatabaseConnection : IDisposable, IAsyncDisposable
{
private NpgsqlConnection? _connection;
private bool _disposed;
public async ValueTask DisposeAsync()
{
if (_disposed) return;
_disposed = true;
if (_connection != null)
{
await _connection.DisposeAsync();
_connection = null;
}
// Không cần GC.SuppressFinalize nếu không có finalizer
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
_connection?.Dispose();
_connection = null;
}
}
// ⚠️ Finalizer chỉ dùng khi wrap unmanaged resource trực tiếp
// Hầu hết trường hợp, dùng SafeHandle thay vì finalizer
5. Hosting Model & Kestrel Internals
ASP.NET Core Hosting Pipeline
Internet
│
┌────────▼────────┐
│ Reverse Proxy │ ← Nginx / IIS / Azure Front Door
│ (TLS, LB, IP) │
└────────┬────────┘
│ HTTP/1.1 or HTTP/2
┌────────▼────────┐
│ Kestrel │ ← Edge server (production-ready)
│ (libuv → I/O) │ .NET 8: io_uring trên Linux
└────────┬────────┘
│
┌────────▼────────┐
│ Host Builder │
│ ┌────────────┐ │
│ │ Middleware │ │ ← Pipeline: Auth, Routing, CORS...
│ │ Pipeline │ │
│ └────────────┘ │
│ ┌────────────┐ │
│ │ Routing │ │ ← Endpoint matching
│ │ Engine │ │
│ └────────────┘ │
│ ┌────────────┐ │
│ │ Endpoints │ │ ← Controllers / Minimal APIs
│ └────────────┘ │
└─────────────────┘
Kestrel Configuration
csharpvar builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(options =>
{
// HTTP/1.1 + HTTP/2 trên port 5000
options.ListenAnyIP(5000, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
});
// HTTPS với HTTP/2 trên port 5001
options.ListenAnyIP(5001, listenOptions =>
{
listenOptions.UseHttps("cert.pfx", "password");
listenOptions.Protocols = HttpProtocols.Http2;
});
// Performance tuning
options.Limits.MaxConcurrentConnections = 1000;
options.Limits.MaxRequestBodySize = 50 * 1024 * 1024; // 50MB
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);
// HTTP/2 settings
options.Limits.Http2.MaxStreamsPerConnection = 100;
options.Limits.Http2.InitialStreamWindowSize = 98304; // 96KB
});
WebApplication vs WebApplicationBuilder (.NET 6+)
csharp// Minimal hosting model (recommended .NET 6+)
var builder = WebApplication.CreateBuilder(args);
// ──── Services (DI Container) ────
builder.Services.AddControllers();
builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddScoped<IOrderService, OrderService>();
// ──── Logging ────
builder.Logging.AddSerilog();
// ──── Configuration ────
// Tự động load: appsettings.json → appsettings.{env}.json
// → Environment Variables → Command line → User Secrets (dev)
builder.Configuration.AddAzureKeyVault(/* ... */);
var app = builder.Build();
// ──── Middleware Pipeline (THỨ TỰ QUAN TRỌNG!) ────
app.UseExceptionHandler("/error"); // 1. Exception handling (outermost)
app.UseHsts(); // 2. HSTS
app.UseHttpsRedirection(); // 3. HTTPS redirect
app.UseStaticFiles(); // 4. Static files (short-circuit)
app.UseRouting(); // 5. Route matching
app.UseCors("MyPolicy"); // 6. CORS
app.UseAuthentication(); // 7. Who are you?
app.UseAuthorization(); // 8. Can you do this?
app.UseRateLimiter(); // 9. Rate limiting
app.MapControllers(); // 10. Execute endpoint
app.Run();
⚠️ Middleware order matters!
UseAuthentication()phải trướcUseAuthorization().UseCors()phải sauUseRouting()nhưng trướcUseAuthorization().
6. .NET 8/9 Highlights Cho Production
.NET 8 LTS (Long-Term Support)
csharp// 1. Frozen Collections - hash table tối ưu cho read-only data
var lookup = new Dictionary<string, int>
{
["VN"] = 84, ["US"] = 1, ["JP"] = 81
}.ToFrozenDictionary(); // System.Collections.Frozen
// ~75% faster lookup so với Dictionary thường
// 2. Time abstraction - testable time
public class OrderService(TimeProvider timeProvider)
{
public Order CreateOrder()
{
return new Order
{
CreatedAt = timeProvider.GetUtcNow(), // testable!
ExpiresAt = timeProvider.GetUtcNow().AddHours(24)
};
}
}
// Test: new OrderService(new FakeTimeProvider(fixedTime))
// 3. Keyed Services DI
builder.Services.AddKeyedSingleton<ICache>("redis", new RedisCache());
builder.Services.AddKeyedSingleton<ICache>("memory", new MemoryCache());
public class ProductService([FromKeyedServices("redis")] ICache cache)
{
// Inject Redis cache cụ thể
}
// 4. Native AOT cho ASP.NET Core
// Minimal APIs fully AOT-compatible trong .NET 8
.NET 9 (STS - Standard Term Support, Nov 2024)
csharp// 1. LINQ improvements
var topProducts = products
.CountBy(p => p.Category) // Đếm theo group, không cần GroupBy().Select()
.Where(g => g.Value > 10);
var indexed = items.Index(); // (index, element) pairs
// 2. System.Text.Json - mới
[JsonConverter(typeof(JsonStringEnumConverter<OrderStatus>))]
public enum OrderStatus { Pending, Shipped, Delivered }
// 3. Task.WhenEach - process tasks as they complete
await foreach (var task in Task.WhenEach(tasks))
{
var result = await task;
Console.WriteLine($"Completed: {result}");
}
// 4. HybridCache - thay MemoryCache + DistributedCache
builder.Services.AddHybridCache(options =>
{
options.DefaultEntryOptions = new()
{
LocalCacheExpiration = TimeSpan.FromMinutes(5),
Expiration = TimeSpan.FromMinutes(30) // distributed
};
});
public class ProductService(HybridCache cache)
{
public async Task<Product> GetProduct(int id)
{
return await cache.GetOrCreateAsync(
$"product:{id}",
async ct => await _db.Products.FindAsync(id, ct),
tags: ["products"]
);
}
}
// 5. Built-in OpenAPI (Scalar thay Swagger)
builder.Services.AddOpenApi();
app.MapOpenApi(); // /openapi/v1.json
7. Diagnostic Cheat Sheet
bash# Xem runtime info
dotnet --info
# Xem GC stats realtime
dotnet-counters monitor --process-id <PID> \
--counters System.Runtime[gen-0-gc-count,gen-1-gc-count,gen-2-gc-count,gc-heap-size]
# Capture trace (performance profiling)
dotnet-trace collect --process-id <PID> --duration 00:00:30
# Memory dump
dotnet-dump collect --process-id <PID>
dotnet-dump analyze <dump-file>
# HTTP benchmarking
dotnet tool install -g dotnet-httprepl
Tổng Kết Session 01
| Concept | Key Takeaway |
|---|---|
| .NET Framework → .NET 9 | Framework chỉ còn support, mọi dự án mới dùng .NET 8/9 |
| CoreCLR | Cross-platform, modular, hiệu năng cao hơn nhiều |
| Tiered JIT + PGO | Startup nhanh (Tier 0) + throughput tối ưu (Tier 1 + PGO) |
| Native AOT | Startup cực nhanh, phù hợp serverless/containers |
| GC | Server GC cho web, DATAS (.NET 8+), tránh LOH allocation |
| Kestrel | Production-ready edge server, HTTP/2 native |
| .NET 8/9 | FrozenCollection, HybridCache, TimeProvider, Native AOT |
Bài tập: Chạy
dotnet-counterstrên 1 ASP.NET Core app của bạn, quan sát GC gen-0/1/2 counts khi load test bằngbombardierhoặck6.