Vấn đề: Strongly Typed Language
C# là ngôn ngữ strongly-typed: mọi biến phải khai báo kiểu tại compile time. Nhưng đôi khi kiểu chỉ biết tại runtime (COM, JSON, scripting, reflection).
Giải pháp: dynamic Keyword
- Bỏ qua compile-time type checking.
- Type resolution chuyển sang runtime (DLR - Dynamic Language Runtime).
- Biến
dynamiccó thể gán bất kỳ giá trị nào. - Tất cả member access (property, method, operator) được resolve tại runtime.
Ví dụ cơ bản
using System;
class Program
{
static void Main()
{
dynamic d = 100;
Console.WriteLine($"Value: {d}, Type: {d.GetType()}"); // Int32
d = "Hello World!!";
Console.WriteLine($"Value: {d}, Type: {d.GetType()}"); // String
d = true;
Console.WriteLine($"Value: {d}, Type: {d.GetType()}"); // Boolean
d = DateTime.Now;
Console.WriteLine($"Value: {d}, Type: {d.GetType()}"); // DateTime
// Gọi method/property không tồn tại → RuntimeBinderException
try
{
d.NonExistentMethod(); // Runtime error!
}
catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException ex)
{
Console.WriteLine($"Runtime error: {ex.Message}");
}
}
}
Kết quả
Value: 100, Type: System.Int32 Value: Hello World!!, Type: System.String Value: True, Type: System.Boolean Value: 01/01/2024 10:30:00, Type: System.DateTime Runtime error: 'System.DateTime' does not contain a definition for 'NonExistentMethod'
Dynamic vs Object vs Var
| Đặc điểm | dynamic | object | var |
|---|---|---|---|
| Type resolution | Runtime (DLR) | Compile-time (System.Object) | Compile-time (inferred) |
| Member access | Bất kỳ member nào (runtime check) | Chỉ Object members | Chỉ inferred type members |
| IntelliSense | Không có | Có (Object members) | Có (full) |
| Compile error | Không (trừ syntax) | Có | Có |
| Runtime error | Có (missing member) | InvalidCastException khi cast | Không (compile-time catch) |
| Performance | Chậm (DLR call site caching) | Nhanh | Nhanh (giống explicit type) |
Use Cases Thực Tế
1. COM Interop (Office, Excel, Word)
// Không cần reference type library, không cần cast liên tục
dynamic excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
excel.Visible = true;
dynamic workbook = excel.Workbooks.Add();
dynamic sheet = workbook.ActiveSheet;
sheet.Cells[1, 1].Value = "Hello Dynamic Excel";
workbook.SaveAs(@"C:\Test.xlsx");
excel.Quit();
2. JSON Parsing Không Cần Model (Quick & Dirty)
using System.Text.Json;
string json = @"{ ""name"": ""John"", ""age"": 30, ""city"": ""Hanoi"" }";
// System.Text.Json - cần model hoặc JsonNode
// dynamic với Newtonsoft.Json (legacy) hoặc JsonElement
dynamic data = JsonSerializer.Deserialize(json); // .NET 6+: JsonNode
// Hoặc dùng Newtonsoft: dynamic data = JsonConvert.DeserializeObject(json);
Console.WriteLine(data.name); // John
Console.WriteLine(data.age); // 30
3. Scripting / Plugin System
// Load assembly, tạo instance, gọi method động
Assembly asm = Assembly.LoadFrom("Plugin.dll");
Type type = asm.GetType("Plugin.MyClass");
dynamic instance = Activator.CreateInstance(instance);
instance.Initialize(config); // Gọi method không biết tại compile time
var result = instance.Execute(input);
4. Dynamic XML/HTML Building
dynamic xml = new DynamicXmlBuilder();
xml.Root(
xml.User(
xml.Name("John"),
xml.Age(30)
)
);
string result = xml.ToString();
// John 30
5. ExpandoObject - Dynamic Object Tùy Chỉnh
using System.Dynamic;
dynamic person = new ExpandoObject();
person.Name = "John";
person.Age = 30;
person.SayHello = new Action(() => Console.WriteLine($"Hi, I'm {person.Name}"));
person.SayHello(); // Hi, I'm John
// Thêm property động
person.Address = new { City = "Hanoi", Country = "Vietnam" };
// Duyệt properties (implements IDictionary)
foreach (var prop in (IDictionary)person)
{
Console.WriteLine($"{prop.Key}: {prop.Value}");
}
Performance & Caching
- DLR sử dụng Call Site Caching: lần đầu gọi chậm, các lần sau nhanh (cache delegate).
- Vẫn chậm hơn static typing ~10-100x tùy scenario.
- Tránh dùng trong hot path, loop lớn, game loop.
Best Practices
- Hạn chế sử dụng - chỉ khi thực sự cần (COM, dynamic JSON, scripting).
- Document rõ ràng expected shape của dynamic object.
- Try-catch RuntimeBinderException ở boundary.
- Ép kiểu về static type sớm nhất có thể:
var typed = (ConcreteType)dynamicObj;. - Không dùng cho DTO/Model - dùng record/class + JSON serializer.
C# 11: Required Properties + Dynamic?
// Dynamic không hỗ trợ required properties check tại compile time
// Runtime: vẫn có thể set sau khi tạo
dynamic d = new ExpandoObject();
// d.RequiredProp = "value"; // Không enforce