.Net – CodeDOM – Tìm hiểu và ứng dụng

CodeDOM (Code Document Object Model) là một API của .Net giúp bạn có thể viết ra những chương trình tự động sinh ra mã lệnh một cách nhanh chóng và dễ dàng. Thậm chí chỉ cần vài dòng code bạn có thể biên dịch mã lệnh C#, JScript, VB.Net thành một tập tin assembly. Vì thế, CodeDom là một bộ thư viện hữu ích nếu như bạn muốn tạo một compiler đơn giản cho các ngôn ngữ trên

Các khái niệm cơ bản

Các lớp cần để sử dụng nằm trong namespace System.CodeDom.

Trước tiên bạn cần hiểu mô hình và các khái niệm cơ bản được sử dụng trong CodeDom.

Trong đó:

  1. CompileUnit: Đơn vị chứa toàn bộ CodeDom với một collection các Namespace.
  2. Namespace: Tên của namespace
  3. NamespaceImport: Các namespace được sử dụng (bằng chỉ thị using trong C#)
  4. Type: class, structure, interface, enumeration
  5. Property, Method, Field: các thành viên của lớp
  6. Statement/ Expression: câu lệnh/ biểu thức trong method hoặc property.

Tạo class Rectangle bằng CodeDom

Để minh họa cho việc sử dụng CodeDom, tôi sẽ tạo một class Rectangle để tính diện tích hình chữ nhật bao gồm phương thức Main và chạy trong Console (bạn không nên nhầm lẫn class này với class Rectangle trong System.Drawing)

Mã nguồn bằng C# được sinh ra từ CodeDom như sau:

Rectangle.cs:

Các bước thực hiện

Để sử dụng CodeDom tạo ra lớp Rectangle (hoặc một class hay assembly bất kì). Bạn thực hiện tuần tự các bước từ cao xuống thấp như trong mô hình CodeDom ở trên.

Phần tạo đối tượng CompileUnit có thể bỏ qua nếu như bạn chỉ cần một namespace. Bạn có thể sinh code và biên dịch trực tiếp từ namespace này.

Trong ví dụ này tôi sẽ viết một phương thức CreateRectangleCode() để tạo ra CodeDom cho đoạn mã Rectangle trên. Phương thức này trả về một đối tượng CompileUnit và sẽ được tạo ra cuối cùng để trả về cho phương thức.

Chú ý: Các bước sau sử dụng khá nhiều lớp khác nhau trong System.CodeDom. Mỗi lớp có công dụng riêng để định nghĩa biến, phương thức, khai báo, gán giá trị,… Các lớp này đều được thừa kế từ hai lớp abstract chính là CodeExpression và CodeStatement. Bạn có thể lúng túng và cảm thấy khó hiểu, tuy nhiên chỉ cần dựa vào tên của lớp bạn có thể biết được công dụng của chúng và thông qua IntelliSense và MSDN là đủ để viết được các đoạn mã cần thiết.

1. Tạo Namespace và import các namespace cần thiết:

 

2. Tạo class và thêm vào namespace

 

3. Tạo các field:

Ta cần hai private field _length, _width kiểu Int32 chứa chiều dài, rộng của hình chữ nhật và thêm vào lớp. Constructor của CodeMemberField có tham số đầu tiên là kiểu và tham số  thứ hai là tên field. Tùy theo overload mà bạn có thể đặt tham số thứ nhất là một chuỗi, một đối tượng kiểu Type hay CodeTypeReference:

Để kết hợp các attribute với nhau, bạn dùng toán tử | (OR), ví dụ:

field1.Attributes = MemberAttributes.Private | MemberAttributes.Static;

4. Tạo các property:

Sau khi có hai field _length và _width, bạn cần tạo hai property tương ứng là Length và Width bằng lớp CodeMemberProperty. Các property bao gồm các thuộc tính:

–               bool HasGet: getter
–               bool HasSet: setter
–               CodeStatementCollection GetStatement: các câu lệnh trong getter
–               CodeStatementCollection SetStatement: các câu lệnh trong setter

Để viết lệnh cho getter cho property này, bạn cần dùng lớp thêm một đối tượng kiểu  CodeMethodReturnStatement vào GetStatement để trả về field _length. Cú pháp như sau:

GetStatements ( return ( field ( this._length ) ) )

Và mã C#:

Với SetStatement ta sẽ dùng CodeAssignStatement để gán dữ liệu từ tham số cho field:

Với propery Width còn lại bạn cũng làm tương tự, chỉ khác phần tên.

5. Tạo Constructor:

Constructor mà ta cần tạo sẽ có hai tham số length và width để khởi tạo giá trị cho hai field tương ứng của class.

Sau đó gán hai tham số này cho các property Length, Width rồi thêm vào class:

6. Tạo phương thức CalculateArea():

Phương thức này ta cần khai báo biến area và gán giá trị Length * Width bằng CodeVariableDeclarationStatement. Ta dùng overload sau của lớp này:

public CodeVariableDeclarationStatement(
Type type,
string name,
CodeExpression initExpression
)


Cuối cùng trả về biến area này cho phương thức:

7. Tạo phương thức Main()

Mặc dù cũng là phương thức nhưng CodeDom có một lớp riêng để định nghĩa một phương thức có Entry Point. Cách sử dụng tương tự như bạn tạo phương thức thông thường, tất nhiên là bạn không cần gán tên cho phương thức này:

CodeEntryPointMethod mainMethod = new CodeEntryPointMethod();

Thêm câu lệnh khai báo và khởi tạo một đối tượng Rectangle trong phương thức Main() với tham số là 5 và 4:

Tiếp tục khai báo một biến area và gọi phương thức CalculateArea của Rectangle để tính diện tích:

Dùng phương thức Console.WriteLine() để in biến area và thêm Main() và class:

8. Đóng gói tất cả vào CompileUnit:

 

Đơn giản hóa công việc bằng CodeSnippetExpression

Việc tạo các statement mà bạn đã làm trên các bước trên khá dài dòng và dễ gây nhầm lẫn. Bạn có thể đơn giản hóa điều này bằng cách dùng lớp CodeSnippetExpression để tạo các statement thông qua các chuỗi lệnh truyền vào.

Ví dụ để tạo ra dòng lệnh sau:

Console.WriteLine(area)

Thay vì dùng CodeMethodInvokeExpression kết hợp với CodeTypeReferenceExpression và CodeVariableReferenceExpression như dưới đây:

Bạn chỉ cần viết:

Hoặc để return biến area cho phương thức. Thay vì:

Ta sử dụng:

Sinh mã và biên dịch với CodeDomProvider

CodeDomProvider là một abstract class với các lớp thừa kế là CSharpCodeProvider, VBCodeProvider và JScriptCodeProvider. Lớp này cung cấp các phương thức GenerateCodeFromXX() và CompileAssemblyFromXX() để sinh ra mã và biên dịch thành assembly từ các đối tượng của CodeDom hoặc mã nguồn.

 

Để thuận tiện, ta tạo một phương thức để tạo ra CodeDomProvider dựa theo ngôn ngữ:

Phương thức để Generate mã nguồn từ CodeDomProvider:

 

Đối tượng CodeGeneratorOptions để xác định các tùy chọn định dạng của mã nguồn được sinh ra. Ví dụ ở trên thuộc tính IndentString được gán là một chuỗi khoảng trắng để quy định chuỗi kí tự thụt đầu dòng ở mỗi dòng lệnh.

Phương thức trên nhận một TextWriter để xác định luồng xuất mã nguồn. Muốn xuất ra một file văn bản bạn tạo một StreamWriter và tạo một StringWriter nếu muốn lưu mã nguồn vào biến chuỗi.

Phương thức để Compile từ CodeDomProvider:

Để compile, ta cần một đối tượng CompilerParameters chứa các tùy chọn biên dịch. Phương thức CompileAssemblyFromDom() biên dịch và trả về một đối tượng CompilerResults chứa kết quả biên dịch. Bạn có thể dựa vào property Errors của đối tượng này để lấy các lỗi phát sinh nếu có. Ví dụ tôi in ra các lỗi phát sinh bằng cách dùng vòng lặp như sau:

Cuối cùng ta tạo một phương thức Main() để kiểm tra mọi thứ:

Output (VB.Net):

hoc-lap-trinh-net

Nhận xét