Sử Dụng Reflection Trong C# Như Thế Nào?

Sử Dụng Reflection Trong C# Như Thế Nào?

Reflection có nghĩa là cách thức để truy cập tới metadata - siêu dữ liệu của loại đối tượng bất kỳ tại thời điểm chương trình đang chạy. Hiểu nôm na có nghĩa là tại thời điểm chương trình đang chạy làm cách nào chúng ta có thể xác định được metadata của một đối tượng như namespace, thuộc tính của lớp - hàm, hàm khởi tạo, phương thức, thuộc tính, cách khởi tạo đối tượng, gọi hàm...

Chúng ta hãy tìm hiểu 2 khái niệm loại đối tượngmetadata để hiểu chi tiết về reflection.

Metadata - gọi là siêu dữ liệu nhưng mình gọi là thông tin cho ngắn gọn nhé :)

Khởi tạo

Mình có tạo ra class ReflectionCSharp sử dụng xuyên suốt trong bài viết được khởi tạo và chạy trong hàm Main như sau:

class Program
    {
        static void Main(string[] args)
        {
            var reflection = new ReflectionCSharp();
            reflection.Run();

            Console.ReadKey();
        }
    }


A. Loại thông tin (Type)

Là lớp abstract đại diện cho toàn bộ các kiểu dữ liệu trong .Net. Sử dụng class này chúng ta có thể xác định được toàn bộ metadata của loại dữ liệu bất kỳ tại thời điểm chương trình đang chạy.
Để xác định loại đối tượng, chúng ta có thể sử dụng phương thức GetType() hoặc toán tử typeof. Nếu là đối tượng đã khởi tạo thì bạn sử dụng phương thức GetType(). Nếu là kiểu dữ liệu thì sử dụng toán tử typeof.
Trong ví dụ dưới đây mình đưa ra thông tin của 3 kiểu dữ liệu là int, double và ReflectionInformation do mình tự định nghĩa.

public class ReflectionInformation
    {
        public ReflectionInformation(int id)
        {
            Id = id;
        }

        public ReflectionInformation(string name, string content)
        {
            Name = name;
            Content = content;
        }

        public int Id { get; set; }
        public string Name { get; set; }
        public string Content { get; set; }
    }

Bạn hãy để ý là class này mình viết 2 constructors.

public class ReflectionCSharp
    {
        public void Run()
        {
            var fullName = string.Empty;
            var assemblyName = string.Empty;
            var constructors = new List<string>();

            var type = typeof(int);
            fullName = type.FullName;
            assemblyName = type.Assembly.FullName;
            var listConstructors = type.GetConstructors().ToList();
            foreach (var item in listConstructors) constructors.Add(item.Name);

            Console.WriteLine("Type.FullName: " + fullName);
            Console.WriteLine("Type.Assembly.FullName: " + assemblyName);
            Console.WriteLine("Type.GetConstructors: " + string.Join(", ", constructors));
            Console.WriteLine("==============================");

            double i = 100d;
            type = i.GetType();
            fullName = type.FullName;
            assemblyName = type.Assembly.FullName;
            constructors.Clear();
            listConstructors = type.GetConstructors().ToList();
            foreach (var item in listConstructors) constructors.Add(item.Name);

            Console.WriteLine("Type.FullName: " + fullName);
            Console.WriteLine("Type.Assembly.FullName: " + assemblyName);
            Console.WriteLine("Type.GetConstructors: " + string.Join(", ", constructors));
            Console.WriteLine("==============================");

            var reflectionInfo = new ReflectionInformation("Name", "Value");
            type = reflectionInfo.GetType();
            fullName = type.FullName;
            assemblyName = type.Assembly.FullName;
            constructors.Clear();
            listConstructors = type.GetConstructors().ToList();
            foreach (var item in listConstructors) constructors.Add(item.ToString());

            Console.WriteLine("Type.FullName: " + fullName);
            Console.WriteLine("Type.Assembly.FullName: " + assemblyName);
            Console.WriteLine("Type.GetConstructors: " + string.Join(", ", constructors));
        }
    }

Và bạn hãy xem kết quả dưới đây


 


Chúng ta cùng đọc qua một số thông số như trên hình nhé:
- Với những dòng có chuỗi Type.FullName ở đầu dòng: thể hiện thông tin tên class của loại đối tượng (bao gồm namespace).

Ví dụ:
+ System.Int32 => struct Int32 nằm trong namespace System
+ System.Double => struct Double nằm trong namespace System
+ HelloCSharp.ReflectionInformation => class ReflectionInformation nằm trong namespace HelloCSharp

- Với những dòng có chuỗi Type.Assembly.FullName ở đầu dòng: thể hiện thông tin assembly (tên file dll hoặc exe), phiên bản...

Ví dụ:
+ mscorlib => file mscorlib.dll - thư viện mặc định của .Net
+ HelloCSharp => file HelloCSharp.exe - file build ứng dụng

- Với những dòng có chuỗi Type.GetConstructors ở đầu dòng: thể hiện danh sách các constructor của class

Ví dụ:
+ Với 2 kiểu dữ liệu int và double loại dữ liệu là struct nên không có constructor
+ Với kiểu dữ liệu là ReflectionInformation có 2 constructor: constructor có 1 tham số int và constructor có 2 tham số string

Ngoài những thông tin trên còn rất nhiều các thông tin hữu dụng khác như xác định các thuộc tính của class, struct - phương thức, kiểu interface, abstract, các methods của đối tượng... Trong phần B mình sẽ liệt kê chi tiết một số các thông tin hay sử dụng nhất.

B. Metadata

Chúng ta viết lại class ReflectionInformation với đầy đủ thông tin gồm các trường, thuộc tính, các hàm và constructors như sau:

public class ReflectionInformation
    {
        private int _id;
        private string _name;

        public int PublicId;
        public string PublicName;

        public ReflectionInformation()
        {

        }

        public ReflectionInformation(int id)
        {
            Id = id;
        }

        public ReflectionInformation(int id, string name)
        {
            Id = id;
            Name = name;
        }

        public int Id { get; set; }
        public string Name { get; set; }

        public void Write()
        {
            Console.WriteLine("Id: " + Id);
            Console.WriteLine("Name: " + Name);
        }

        public void Write(string name)
        {
            Console.WriteLine("Name: " + name);
        }
    }


1. Attributes


Là những thông tin ngoài thông tin mặc định của .Net. Những thông tin thêm này có thể được gán cho class, trường (field), thuộc tính (property), method... Để khai báo thêm thông tin, thì thông tin phải nằm trong class thừa kế từ lớp System.Attribute.

public class CustomAttribute : Attribute
    {
        public string Name { get; set; }

        public void Write()
        {
            Console.WriteLine("Hello CustomAttribute.");
        }
    }


Lưu ý: cách đặt tên của một thuộc tính nên là <Tên thuộc tính>Attribute và dùng <Tên thuộc tính> để gán cho lớp, thuộc tính, methods...

Với thuộc tính Custom như trên, chúng ta có thể gán cho lớp, trường, thuộc tính, hàm... như đoạn code dưới đây:

[Custom]
    public class ReflectionInformation
    {
        [Custom]
        private int _id;

        private string _name;

        [Custom]
        public ReflectionInformation()
        {

        }

        public ReflectionInformation(int id)
        {
            Id = id;
        }

        public ReflectionInformation(int id, string name)
        {
            Id = id;
            Name = name;
        }

        [Custom]
        public int Id { get; set; }
        public string Name { get; set; }

        [Custom]
        public void Write()
        {
            Console.WriteLine("Id: " + Id);
            Console.WriteLine("Name: " + Name);
        }

        public void Write(string name)
        {
            Console.WriteLine("Name: " + name);
        }
    }


Ví dụ sẽ được viết lại như sau để xác định attribute mặc định và attribute được viết thêm:

public class ReflectionCSharp
    {
        public void Run()
        {
            var reflectionInfo = new ReflectionInformation();
            var type = reflectionInfo.GetType();

            var attrs = type.Attributes;
            Console.WriteLine("Class.Attribute: " + attrs);

            var customAttrs = type.CustomAttributes;
            Console.WriteLine("Class.Custom.Attribute: " + customAttrs);
        }
    }


Kết quả khi chạy chương trình:



Dòng đầu tiên hiển thị danh sách các attribute mặc định của .Net.
Dòng thứ 2 hiển thị danh sách các attribute do người lập trình tự định nghĩa.


2. ConstructorInfo

Xác định danh sách các constructors - mặc định là public constructors.

public class ReflectionCSharp
    {
        public void Run()
        {
            var reflectionInfo = new ReflectionInformation();
            var type = reflectionInfo.GetType();

            var constructors = type.GetConstructors();
            foreach (var item in constructors) Console.WriteLine(item);
        }
    }


Kết quả của việc xác định danh sách các constructors như sau:



Trong ví dụ trên, chúng ta thấy rằng class có 3 constructors bên cạnh đó còn có các thông tin về kiểu dữ liệu của tham số truyền vào.

3. MethodInfo

Xác định danh sách các methods - mặc định là public methods

public class ReflectionCSharp
    {
        public void Run()
        {
            var reflectionInfo = new ReflectionInformation();
            var type = reflectionInfo.GetType();

            var methods = type.GetMethods();
            foreach (var item in methods) Console.WriteLine(item);
        }
    }

Kết quả của việc chạy ứng dụng như sau:

   

Bên cạnh việc xác định các public methods, chúng ta có thể xác định các private - protected methods thông qua hàm GetMethods() với tham số BindingFlags.

4. PropertyInfo

Xác định danh sách các properties - mặc định là public properties

public class ReflectionCSharp
    {
        public void Run()
        {
            var reflectionInfo = new ReflectionInformation();
            var type = reflectionInfo.GetType();

            var props = type.GetProperties();
            foreach (var item in props) Console.WriteLine(item);
        }
    }


Kết quả như sau:


5. FieldInfo

Xác định danh sách các fields - mặc định là public fields

public class ReflectionCSharp
    {
        public void Run()
        {
            var reflectionInfo = new ReflectionInformation();
            var type = reflectionInfo.GetType();

            var fields = type.GetFields();
            foreach (var item in fields) Console.WriteLine(item);
        }
    }


Kết quả như sau:



Lưu ý: trong các thông tin trên chúng ta đều nói đến việc xác định các thông tin có mức truy cập là public. Vậy những thông tin có mức truy cập là private, protected, internal thì có lấy được không? Câu trả lời là có các bạn nhé, để xác định những thông tin đó, bạn cần sử dụng và kết hợp enum BindingFlags (thuộc namspace System.Reflection).

Trên đây là những thông tin quan trọng nhất mà một class bắt buộc phải có. Để có thể xác định những thông tin khác, chúng ta có thể vào document của Microsoft hoặc tra cứu trên google.

C. Ứng dụng


Sau một hồi trình bày lý thuyết sùi bọt mép, giờ đến phần quan trọng là áp dụng reflection vào ứng dụng của chúng ta như nào? Qua quá trình làm việc của mình thì mình xin đưa ra 2 ý để ứng dụng reflection (không tính trường hợp xác định thông tin nhé) như sau:

1. Khởi tạo đối tượng, gọi method của đối tượng đó lúc chương trình đang chạy

Bình thường khi chúng ta tạo một đối tượng, chúng ta sử dụng từ khóa new nhưng chúng ta cũng có thể tạo ra đối tượng bằng cách sử dụng thư viện System.Activator

public class ReflectionCSharp
    {
        public void Run()
        {
            var reflectionInfo = new ReflectionInformation();
            var type = reflectionInfo.GetType();

            var firstReflection = (ReflectionInformation) Activator.CreateInstance(type, new object[] { 10 });  //  Một tham số
            var secondReflection = (ReflectionInformation) Activator.CreateInstance(type, new object[] { 10, "Name" });  //  Hai tham số
        }
    }


Mặc định hàm Activator.CreateInstance trả về kiểu object. Thực hiện ép kiểu để đưa về đối tượng mong muốn. Hàm trên với tham số đầu tiên là loại đối tượng tạo, tham số thứ 2 là danh sách các parameters ứng với constructors cụ thể. Sau đó chúng ta có thể gọi phương thức như đoạn code thêm sau:

public class ReflectionCSharp
    {
        public void Run()
        {
            var reflectionInfo = new ReflectionInformation();
            var type = reflectionInfo.GetType();

            var firstReflection = (ReflectionInformation) Activator.CreateInstance(type, new object[] { 10 });  //  Một tham số
            var secondReflection = (ReflectionInformation) Activator.CreateInstance(type, new object[] { 10, "Name" });  //  Hai tham số

            firstReflection.Write();    //  Output: Id: 10. Name:
            secondReflection.Write();    //  Output: Id: 10. Name: Name
        }
    }

Việc gọi hàm ngoài cách như trên còn cách thứ 2 khác là xác định hàm cần gọi, danh sách các tham số truyền vào và thực hiện việc gọi hàm. Mình không khuyến khích sử dụng cách thứ 2 nên sẽ không đi chi tiết để tránh mất thời gian. 

2. Xác định các class mà thừa kế từ class khác hoặc implement từ interface

Giả sử chúng ta có interface như sau:

public interface IValidate
    {
        bool IsOk(string text);
    }


Việc Validate nội dung này bạn chia cho n thành viên trong nhóm. Mình đưa ra 2 validate dưới đây làm ví dụ:

public class TextNotEmpty : IValidate
    {
        public bool IsOk(string text)
        {
            return !string.IsNullOrEmpty(text);
        }
    }

public class TextAtLeast8Chars : IValidate
    {
        public bool IsOk(string text)
        {
            return text.Length >= 8;
        }
    }


Để xác định danh sách các validate khi chương trình đang chạy, chúng ta sử dụng đoạn code sau:

public class ReflectionCSharp
    {
        public void Run()
        {
            var type = typeof(IValidate);

            var needValids = AppDomain.CurrentDomain.GetAssemblies()
                                            .SelectMany(s => s.GetTypes())
                                            .Where(p => type.IsAssignableFrom(p) && !p.IsInterface && !p.IsAbstract);
            foreach (var item in needValids) Console.WriteLine(item);
        }
    }


Kết quả chúng ta có được 2 class validate như sau:



Và giờ chúng ta có thể thực hiện validate bất kỳ đoạn text nào như sau:

public class ReflectionCSharp
    {
        public void Run()
        {
            var type = typeof(IValidate);

            var needValids = AppDomain.CurrentDomain.GetAssemblies()
                                            .SelectMany(s => s.GetTypes())
                                            .Where(p => type.IsAssignableFrom(p) && !p.IsInterface && !p.IsAbstract);
            var text = string.Empty;
            foreach (var item in needValids)
            {
                var o = Activator.CreateInstance(item, null) as IValidate;
                var ok = o.IsOk(text);

                Console.WriteLine(item + "==" + text + "==" + ok);
            }
            Console.WriteLine("==================");

            text = "WTF";
            foreach (var item in needValids)
            {
                var o = Activator.CreateInstance(item, null) as IValidate;
                var ok = o.IsOk(text);

                Console.WriteLine(item + "==" + text + "==" + ok);
            }
            Console.WriteLine("==================");

            text = "WTF WTF WTF";
            foreach (var item in needValids)
            {
                var o = Activator.CreateInstance(item, null) as IValidate;
                var ok = o.IsOk(text);

                Console.WriteLine(item + "==" + text + "==" + ok);
            }
        }
    }

Kết quả như sau:

 

Kết luận

Như vậy là mình đã giới thiệu tới các bạn về reflection và ứng dụng của nó. Rất mong những chia sẻ này sẽ có ích với các bạn. Hẹn gặp lại các bạn trong các bài viết tiếp theo. :)