CodeGym /Các khóa học /C# SELF /Các kiểu động ( dynamic

Các kiểu động ( dynamic) và DLR

C# SELF
Mức độ, Bài học
Có sẵn

1. Giới thiệu

C# — đại diện của các ngôn ngữ kiểu mạnh. Điều đó có nghĩa là ở bước biên dịch biến phải có kiểu xác định, và truy cập các thành viên của lớp chỉ được theo kiểu đó. Điều này an toàn, tiện lợi, IDE và compiler hiểu được.

Nhưng đôi khi cần truy cập các đối tượng có cấu trúc có thể không biết được ở thời điểm biên dịch — ví dụ, khi:

  • Thực thi mã viết bằng ngôn ngữ .NET khác (ví dụ IronPython hoặc IronRuby), nơi kiểu là động.
  • Khi làm việc với COM-objects (ví dụ với Microsoft Office qua Interop).
  • Khi tương tác với các cấu trúc dữ liệu động: ví dụ các JSON ít được chuẩn hóa.
  • Không muốn viết nhiều mã mẫu, mà đơn giản “tin” đối tượng và thử gọi phương thức cần theo tên.

Kể từ C# 4.0 với từ khóa dynamic và nền tảng DLR, có thể viết mã “như trong Python, nhưng bằng C#”: kiểm tra sẽ diễn ra ở runtime, không phải lúc biên dịch. Qua từ khóa này có thể bắt compiler bớt nghiêm ngặt và tự kiểm tra xem có phương thức/thuộc tính khi chương trình thực sự chạy.

DLR là gì?

Dynamic Language Runtime (DLR) — là phần mở rộng hạ tầng cho CLR (Common Language Runtime), cho phép hỗ trợ các ngôn ngữ động (và hành vi động trong các ngôn ngữ tĩnh). DLR đảm nhiệm việc thực hiện các thao tác không thể được kiểm tra bởi compiler: ví dụ gọi phương thức theo tên khi kiểu chỉ biết được lúc chạy. Nhờ DLR, C# có “chế độ động”.

2. Từ khóa dynamic: cách nó hoạt động

Khác với object

Một số người mới nghĩ rằng dynamic chỉ là cách sành điệu hơn để viết object và mỗi lần phải cast. Không phải vậy.

  • Biến kiểu object luôn yêu cầu cast rõ ràng sang kiểu cụ thể để gọi phương thức (và compiler kiểm tra điều đó).
  • Biến kiểu dynamic ra lệnh cho compiler: “tin tớ, tớ biết kiểu ở đó là gì — xử lý khi cần!”. Nếu phương thức không tồn tại — sẽ có ngoại lệ ở runtime.

Ví dụ:


// Primer s object
object obj = "Privet, C#!";
// obj.ToUpper(); // Lỗi biên dịch! Compiler không biết obj là string
string s = ((string)obj).ToUpper(); // Chỉ có cách này

// Primer s dynamic
dynamic dyn = "Privet, C#!";
string upper = dyn.ToUpper(); // Compiler không phản đối, kiểm tra sẽ ở runtime

Với dynamic có thể truy cập các thuộc tính và phương thức không tồn tại. Nếu chúng không có — sẽ ném ngoại lệ kiểu RuntimeBinderException.

Sử dụng trong ví dụ ứng dụng

Giả sử trong ứng dụng nhỏ của ta có phần làm việc với dữ liệu JSON, cấu trúc có thể thay đổi khi chạy. Với dynamic có thể truy cập thuộc tính như thể chúng luôn tồn tại:


dynamic user = new System.Dynamic.ExpandoObject();
user.Name = "Vasya";
user.Age = 35;
Console.WriteLine($"Imya: {user.Name}, vozrast: {user.Age}");

3. DLR hoạt động như thế nào: bên dưới nắp máy

Khi biến được khai báo là dynamic, compiler C# ghi nhớ rằng mọi truy cập tới phương thức, thuộc tính, indexer, toán tử v.v. trên đối tượng này không thể kiểm tra trước.

Thay vào đó, ở chỗ gọi động sẽ được chèn mã gọi tới “dispatcher” hệ thống. DLR kiểm tra: phương thức hoặc thuộc tính mà ta gọi thực sự tồn tại không? Nếu có — thực hiện gọi, nếu không — ném ngoại lệ.

Sơ đồ trực quan


+------------------------+
| Kod s dynamic          |
+----------+-------------+
           |
           v
+----------+-------------+
| Kompilyator C# vstavlyaet|
| "zapros k DLR"          |
+----------+-------------+
           |
           v
+----------+-------------+
| V runtime DLR ishchet   |
| metod/svoystvo          |
+----------+-------------+
           |
      /---------\
      |         |
   naydeno   ne naydeno
      |         |
      v         v
  vyzov      isklyuchenie
  metoda     (RuntimeBinderException)

DLR lo liệu việc tìm phương thức và thuộc tính trên đối tượng — và thậm chí cache các cuộc gọi động dùng thường xuyên!

Một chút lịch sử

DLR được tạo để hỗ trợ các ngôn ngữ vốn động: IronPython, IronRuby, và để dễ tích hợp với đối tượng động trong C#. Hôm nay nó được dùng khi làm việc với ExpandoObject, DynamicObject, COM và dữ liệu được serialize động.

4. So sánh: static vs dynamic

So sánh cách gọi thuộc tính và phương thức trong mô hình tĩnh và động:

Phương thức Khai báo Kiểm tra thời điểm biên dịch Hành vi khi lỗi
object
object obj = ...
Yêu cầu cast sang kiểu cần Lỗi biên dịch hoặc InvalidCastException
dynamic
dynamic dyn = ...
Không, mọi thứ ở runtime RuntimeBinderException, nếu phương thức không có
kiểu tĩnh
MyClass a = ...
Được compiler kiểm tra Lỗi biên dịch

Ví dụ minh họa:


// Static typing
MyUser u = new MyUser();
u.PrintInfo(); // Compiler kiểm tra

// dynamic
dynamic du = new MyUser();
du.PrintInfo(); // Compiler không kiểm tra, tìm phương thức chỉ ở runtime

// object
object ou = new MyUser();
// ou.PrintInfo(); // Lỗi biên dịch
((MyUser)ou).PrintInfo(); // Phải cast rõ ràng

Phương thức tĩnh và dynamic


dynamic d = "Hello";
Console.WriteLine(d.Length); // 5 — ok

Console.WriteLine(d.FakeMethod()); // RuntimeBinderException khi chạy

Nhưng phải cẩn thận! Nếu bạn đánh máy sai tên hoặc phương thức không tồn tại — lỗi chỉ hiện ra khi app đang chạy. Ví dụ kinh điển "lỗi ở production".

5. Ví dụ thực tế: dynamic trong nhiệm vụ thực

Làm việc với ExpandoObject


using System.Dynamic;

dynamic person = new ExpandoObject();
person.Name = "Sergey";
person.Greet = (Action)(() => Console.WriteLine($"Privet, {person.Name}!"));

person.Greet(); // Vyvedet: Privet, Sergey!

Ở đây ta thêm thuộc tính Name tại runtime và thậm chí delegate Greet. Rất giống thao tác với object trong JavaScript.

Ràng buộc với cấu trúc JSON


using System.Dynamic;
using Newtonsoft.Json;

string json = @"{""name"": ""Andrey"", ""age"": 30}";
dynamic obj = JsonConvert.DeserializeObject
  
   (json); Console.WriteLine(obj.name); // Andrey Console.WriteLine(obj.age); // 30 
  

Lưu ý: nếu truy cập obj.surname — sẽ lỗi khi chạy.

COM-Interop (Office Automation)


dynamic excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application"));
excel.Visible = true; // Thấy được thuộc tính, dù compiler không biết kiểu

6. Những mẹo hữu ích

DLR, dynamic và dùng với các ngôn ngữ .NET khác

  • IronPython/IronRuby: có thể khởi tạo object từ các ngôn ngữ đó và gọi phương thức từ C# qua dynamic.
  • COM-objects: wrapper tự động cho phương thức và thuộc tính thông qua dynamic.
  • Custom dynamic objects: implement interface IDynamicMetaObjectProvider để tạo lớp của bạn với các cuộc gọi động.

Ví dụ tích hợp với IronPython


using IronPython.Hosting;
using Microsoft.Scripting.Hosting;

ScriptEngine engine = Python.CreateEngine();
dynamic py = engine.Execute("class Test: pass\nTest()");
py.x = 42;
Console.WriteLine(py.x); // 42

Cách tự hiện thực kiểu động

Thường dễ hơn khi kế thừa từ DynamicObject.


using System.Dynamic;

class MyDynamic : DynamicObject
{
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (binder.Name == "Ping")
        {
            result = "Pong!";
            return true;
        }
        result = null;
        return false;
    }
}

dynamic d = new MyDynamic();
Console.WriteLine(d.Ping); // Pong!
Console.WriteLine(d.Miss); // null (ne vybrosyvaet isklyuchenie)

Khi nào dynamic có hại

  • Giảm hiệu năng (cuộc gọi động chậm hơn cuộc gọi tĩnh).
  • Làm khó debug và bảo trì.
  • Mất hầu hết tiện ích của IDE: autocomplete, refactoring, find usages.
  • Có thể dẫn tới lỗi chỉ phát hiện bởi người dùng hoặc trên server.

Khi nào nên dùng dynamic

  • Tích hợp với thư viện bên ngoài, nơi không thể hoặc không nên viết wrapper (COM, Python, JavaScript).
  • Thao tác với cấu trúc ít kiểu (JSON/XML/ExpandoObject).
  • Viết framework, metaprogramming và API linh hoạt cần động theo thiết kế.

Trong các trường hợp khác, tốt hơn là dùng kiểu tĩnh và tận hưởng sự trợ giúp của compiler :-)

7. Các lỗi thường gặp khi làm việc với dynamic và DLR

Lỗi #1: Truy cập thành viên không tồn tại.
Gọi phương thức hoặc thuộc tính không tồn tại trên dynamic dẫn tới RuntimeBinderException ở runtime, khó bắt trước khi phát triển.

Lỗi #2: Bỏ qua kiểm tra kiểu.
Nếu không kiểm tra kiểu trước khi gọi (ví dụ if (obj is IDictionary<string, object>)) có thể gặp lỗi bất ngờ khi runtime.

Lỗi #3: Dùng dynamic trong API public.
Kiểu động làm khó duy trì code, vì IDE và analyzer không thể gợi ý thành viên đúng, giảm khả năng đọc hiểu.

Lỗi #4: Lạm dụng dynamic trong mã cần hiệu năng.
Cuộc gọi động chậm hơn tĩnh và tăng overhead. Dùng dynamic chỉ khi cần.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Sử dụng đơn giản `dynamic`
Sử dụng đơn giản `dynamic`
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION