Thứ Ba, 28 tháng 1, 2014

DLR – Dynamic Programming: ExpandoObject, DynamicObject và Dynamic Method Bags

Một điểm chung là khi bạn tạo các đối tượng ExpandoObject và DynamicObject (mà tôi sẽ giới thiệu dưới đây) theo cách thông thường, bạn sẽ thấy rằng đối tượng này chẳng có tác dụng gì. Vì vậy khi nói đến Dynamic programming, bạn hãy dùng từ khóa dynamic thay cho các từ khóa khai báo kiểu dữ liệu khác.

Expando Object

Bản chất của ExpandoObject là một lớp được  implement các interface ICollection, IDictionary, IEnumerable để quản lý các giá trị được thêm vào theo cặp .
Các property tự động xác định kiểu khi bạn tạo ra chúng. Khi để chuột vào một property của đối tượng ExpandoObject, bạn có thể thấy rằng nó cũng được khai báo với dynamic.
Ví  dụ thêm hai property Name và Age:
dynamic expo = new ExpandoObject();
expo.Name = "p1974v04" id="p1974v04_2">Phuc Ngo";
expo.Age = 100;
Console.WriteLine(expo.Name);
Console.WriteLine(expo.Age*2);

Output:
Phuc Ngo
200
Không chỉ gán các giá trị dữ liệu thông thường, bạn có thể gán một delegate cho đối tượng để sử dụng như một phương thức. Nếu chưa quen thuộc với cú pháp tôi sử dụng dưới đây, bạn có thể xem bài viết về Lambda Expression.
1
2
3
4
5
6
dynamic expo = new ExpandoObject();
expo.Power = (Func<double, double, double>)((x, y) => Math.Pow(x, y));
expo.View = (Action)(() => Console.WriteLine(expo.Power));
Console.WriteLine(expo.Power(2,10));
expo.View();
Ouput:
1024
System.Func`3[System.Double,System.Double,System.Double]

Dynamic Object

Để sử dụng lớp này bạn bắt buộc phải tạo một lớp thừa kế từ nó, sau đó override các phương thức tùy theo mục đích bạn cần sử dụng. Linh hoạt hơn ExpandoObject, bạn không chỉ thêm các thành viên cho đối tượng mà còn xử lý được các toán tử binary, unary, ép kiểu, …
Các phương thức mà bạn có thể override để xử lý cho từng trường hợp mà đối tượng được sử dụng có tên bắt đầu bằng Try:
NameDescription
TryBinaryOperationĐược thực thi khi sử dụng các phép toán toán tử hai ngôi như +, -, *, /.  Ví dụ như x+y thì x (left hand side) chính là đối tượng được thực thi phương thức này, với tham số là y.
TryConvertThực thi khi dùng các phép chuyển đổi kiểu, bao gồm không tường minh. Ví dụ:(int)x // explicity = x   // implicit
TryCreateInstanceThis method is not intended for use in C# or Visual Basic.
TryDeleteIndexThis method is not intended for use in C# or Visual Basic.
TryDeleteMemberThis method is not intended for use in C# or Visual Basic.
TryGetIndexDùng với tính năng indexer, lấy giá trị tại vị trí xác định. Ví dụ:x[i]Mặc dù tham số thứ hai indexes của phương thức là một mảng các index cần lấy giá trị, nhưng trong C# ta chỉ được sử dụng phần tử đầu tiên: indexes[0].
TryGetMemberLấy giá trị property của đối tượng. Ví dụ:x.Length
TryInvokeThực thi đối tượng như một delegate. Ví dụ:x(10)
TryInvokeMemberThực thi một thành viên của đối tượng, như method. Ví dụ:x.Test()
TrySetIndexDùng với tính năng indexer, gán giá trị tại vị trí xác định
TrySetMemberGán giá trị cho một thành viên của đối tượng. Ví dụ:x.Name=”Yin”
TryUnaryOperationThực thi khi dùng toán tử unary với đối tượng. Ví dụ:!x-x
Lớp này rất thích hợp để tạo ra wrapper cho một lớp khác. Thay vì dùng các tính năng indexer, operator overloading, viết thêm các method, property thì ta chỉ cần override các phương thức. Mặc dù có vài khuyết điểm như làm giảm tốc độ thực thi và không hỗ trợ Intellisense, nhưng đây là một tính năng đáng để bạn thử.
Ví dụ sau tạo ra một lớp StudentList là wrapper của List và thừa kế từ DynamicObject. Với hai phương thức được override là TryInvokeMember() và TryGetIndex() , bạn có thể sử dụng các phương thức của List và lấy giá trị của phần tử trong List bằng indexer.
Bạn có thể cũng cần tham khảo các bài viết về reflection và LINQ nếu như chưa hiểu rõ cú pháp sử dụng trong ví dụ.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Student
{
    public string ID { get; set; }
    public string Name { get; set; }
    public override string ToString()
    {
        return String.Format("Student: ID = {0}, Name = {1}", ID, Name);
    }
}
class StudentList : DynamicObject
{
    IList students;
    public StudentList()
    {
        students = new List();
    }
    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        MethodInfo method = students.GetType().GetMethod(binder.Name);
        if (method != null)
        {
            result = method.Invoke(students, args);
            return true;
        }
        result = null;
        return false;
    }
    public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
    {
        if (indexes[0].GetType() == typeof(int)) // Lấy student tại index
        {
            result = students[(int)indexes[0]];
        }
        else    // lấy student đầu tiên có ID phù hợp
        {
            string id = indexes[0].ToString();
            result = (from st in students
                        where st.ID == id
                        select st).First();
        }
        return true;
    }
}
Phương thức TryGetIndex() sẽ hỗ trợ lấy phần tử trong đối tượng students bằng indexer theo hai cách: dựa vào index (int) và ID (string).
Phương thức Main() để kiểm tra sẽ cho ra kết quả đúng như ta mong đợi:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Program
{
    static void Main(string[] args)
    {
        dynamic students = new StudentList();
        students.Add(new Student() { ID = "1", Name = "Bo" });
        students.Add(new Student() { ID = "2", Name = "Bi" });
        Console.WriteLine(students["1"]); // ID
        Console.WriteLine(students[1]); // index
        Console.Read();
    }
}
Ouput:
Student: ID = 1, Name = Bo
Student: ID = 2, Name = Bi
Trong bài viết sau tôi sẽ giới thiệu một wrapper của string để minh họa đầy đủ hơn lớp DynamicObject này.

Dynamic Method Bag

Kĩ thuật này được giới thiệu trên MSDN bởi tác giả Bill WagnerDynamic Method Bag sử dụng DynamicObject kết hợp giữa generic, Expression Tree và dynamic để tạo một “method bag”- một kiểu đối tượng cho phép bạn thêm phương thức và sử dụng chúng trong quá trình runtime.
Về bản chất đối tượng Method Bag sẽ lưu trữ các delegate và Expression Tree trong một collection tương tự như ExpandoObject.