Note: see the link below for the English version of this article.

https://duongnt.com/fastandfaster

Thư viện FastAndFaster và dynamic code generation

Vào năm ngoái tôi có viết một bài về dynamic code generation trong C# .NET. Vào đầu năm nay, công ty cũ của tôi (Medidata Solutions) đã tạo điều kiện để tôi dành 2 tuần phát triển ý tưởng trong bài đó thành một thư viện mã nguồn mở. Hôm nay, chúng ta sẽ cùng tìm hiểu thư viện FastAndFaster. Thư viện này có thể được dùng để tạo object và gọi hàm của chúng tại runtime.

Các bạn có thể tải về source code từ đường link dưới đây.

https://github.com/duongntbk/FastAndFaster

Hoặc các bạn có thể tải library từ Nuget.

dotnet add package FastAndFaster

Ý tưởng chính

Để biết cách sử dụng FastAndFaster, các bạn có thể tham khảo hướng dẫn này và tài liệu tổng quan này. Trong bài hôm nay, chúng ta sẽ điểm qua một số chi tiết đáng chú ý.

Về cơ bản FastAndFaster sử dụng chung những kỹ thuật tôi đã giới thiệu ở bài trước. Tuy nhiên, nó có một số cải tiến dưới đây.

  • Code sinh delegate để tạo instance của class được đặt trong class Initializer.
  • Code sinh delegate để gọi hàm được đặt trong class Invocator. Ta có thể gọi được hàm instance, hàm static, cũng như hàm generic.
  • Tất cả delegate được lưu vào cache để dễ cho việc tái sử dụng. Theo thiết lập mặc định, giá trị trong cache sẽ hết hạn sau 12 giờ. Nhưng ta có thể thay đổi thiết lập này.
  • Hỗ trợ gọi cả hàm static và generic.

Xử lý hàm generic

Như đã biết, cách gọi hàm không generic là tương đối đơn giản. Như thấy ở đây, ta chỉ cần gọi hàm GetMethod để tìm MethodInfo của hàm. Nếu hàm có nhiều overload, ta dùng type của các parameter để tìm đúng MethodInfo. Sau đó ta dùng nó để tạo delegate.

methodInfo = type.GetMethod(methodName, parameterTypes);

Tuy nhiên, giải pháp trên không phù hợp với hàm generic. Tại thời điểm ta cần lấy MethodInfo, hàm của ta chưa nhận được giá trị của biến generic type. Trong khi đó, tham số Type[] của hàm GetMethod không th1 chứa unbound type. Vì thế ta không thể dùng nó để tìm MethodInfo. Thay vào đó, ta phải dùng giải pháp này.

  • Lưu danh sách generic type vào một class gọi là GenericInfo. Class này cũng lưu index của từng generic type trong danh sách các parameter của hàm.
  • Gọi hàm GetMethods để lấy tất cả MethodInfo thuộc về class. Sau đó lọc chúng để lấy ra tất cả các MethodInfo của hàm generic cùng tên.
    var candidates = type.GetMethods().Where(n => n.Name == methodName && n.IsGenericMethod);
    
  • Với từng hàm generic đó, kiểm tra xem generic type có đúng index trong danh sách các tham số hay không. Nếu thấy hàm nào khớp tất cả các điều kiện thì MethodInfo của hàm đó sẽ được trả về.
  • Dùng danh sách type lưu trong GenericInfo để gọi hàm MakeGenericMethod để tạo constructed method.

Sau các bước trên, ta thu được MethodInfo cần có để tạo delegate.

Xử lý hàm static

IL của hàm static và hàm không static đều có cấu trúc gần giống nhau. Điểm khác biệt lớn nhất là với hàm không static, ta cần lưu instance của class lên stack. Còn đối với hàm static thì không có instance nào để lưu cả. Bước này tương ứng với IL dưới đây.

IL_0000:  ldarg.0
IL_0001:  castclass  <target class>

Trong FastAndFaster, ta kiểm tra property methodInfo.IsStatic để xem hàm có phải là static hay không. Nếu hàm không phải là static thì ta sẽ lưu instance của class lên stack bằng hàm LoadTarget.

il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, type);

Với hàm static thì ta có thể bỏ qua đoạn code trên.

Xử lý value type

Như đã nhắc đến trong phần Value type và boxing, ta sẽ phải thực hiện boxing và unboxing. Thư viện của ta lưu tất cả tham số và giá trị trả về với type là object. Vì thế ta phải unbox những tham số kiểu value và box giá trị trả về kiểu value.

  • Hàm LoadArguments kiểm tra từng tham số xem nó có phải là kiểu value hay không và đưa ra xử lý phù hợp.
    if (parameterTypes[i].IsValueType)
    {
        il.Emit(OpCodes.Unbox_Any, parameterTypes[i]);
    }
    else
    {
        il.Emit(OpCodes.Castclass, parameterTypes[i]);
    }
    
  • Tương tự thế, hàm ExecuteMethod kiểm tra xem có cần box giá trị trả về trước khi sinh OpCodes.Ret hay không.
    if (methodInfo.ReturnType != typeof(void) && methodInfo.ReturnType.IsValueType)
    {
        il.Emit(OpCodes.Box, methodInfo.ReturnType);
    }
    

Kết thúc

Viết FastAndFaster là một trải nghiệm thú vị đối với tôi. Tôi hy vọng các bạn sẽ thấy nó hữu ích. FastAndFaster là hoàn toàn miễn phí, vì vậy xin hãy thử dùng nó. Và nếu các bạn có góp ý hay ý tưởng mới nào thì xin hãy tạo pull request.

A software developer from Vietnam and is currently living in Japan.

One Thought on “FastAndFaster và dynamic code generation”

Leave a Reply