Kiểu Dữ Liệu Dynamic và Toán Tử Null-Coalescing (??) trong C#

Kiểu dữ liệu dynamic trong C# cung cấp khả năng thao tác với các đối tượng mà việc kiểm tra kiểu sẽ được hoãn lại cho đến thời điểm chạy. Điều này mang lại sự linh hoạt tương tự như lập trình động hoặc các ngôn ngữ kịch bản, cho phép bạn gọi các phương thức hoặc truy cập thuộc tính mà không cần biết kiểu chính xác tại thời điểm biên dịch.

1. Sử dụng dynamic với ExpandoObject

Để tạo một đối tượng dynamic có thể tự do thêm hoặc bớt các thuộc tính và phương thức tại thời điểm chạy, chúng ta thường sử dụng lớp ExpandoObject từ namespace System.Dynamic. Đây là một cách thuận tiện để làm việc với các cấu trúc dữ liệu linh hoạt mà không cần định nghĩa kiểu tường minh trước.

using System;
using System.Dynamic;

public class ViDuDynamic
{
    public static void Main(string[] args)
    {
        dynamic thongTinNguoiDung = new ExpandoObject();
        thongTinNguoiDung.HoTen = "Nguyen Van A";
        thongTinNguoiDung.Tuoi = 30;
        thongTinNguoiDung.NgheNghiep = "Kỹ sư phần mềm";
        thongTinNguoiDung.ThanhPho = "Hà Nội";

        Console.WriteLine($"Thông tin: {thongTinNguoiDung.HoTen}, {thongTinNguoiDung.Tuoi} tuổi, làm {thongTinNguoiDung.NgheNghiep} tại {thongTinNguoiDung.ThanhPho}.");
    }
}

Khi thực thi đoạn mã trên, bạn sẽ thấy thông tin được in ra console một cách chính xác, mặc dù các thuộc tính HoTen, Tuoi, NgheNghiep, ThanhPho không được định nghĩa trong bất kỳ lớp nào mà được thêm vào đối tượng thongTinNguoiDung một cách động.

2. dynamic và Cơ Chế Duck Typing trong Generic

dynamic cũng có thể được dùng để áp dụng cơ chế duck typing (nghĩa là "nếu nó kêu như vịt, đi như vịt, thì nó là vịt") cho các phương thức generic. Điều này cho phép một phương thức generic hoạt động với bất kỳ kiểu nào miễn là kiểu đó hỗ trợ các phép toán hoặc phương thức được gọi tại thời điểm chạy.

using System;

public static class ThucHienPhepToan
{
    // Phương thức generic này có thể thực hiện phép cộng trên các kiểu khác nhau
    public static T Tong<T>(T giaTri1, T giaTri2)
    {
        dynamic dong1 = giaTri1;
        dynamic dong2 = giaTri2;
        return (T)(dong1 + dong2); // Phép cộng sẽ được giải quyết tại runtime
    }
}

public class ChuongTrinh
{
    public static void Main(string[] args)
    {
        int ketQuaSoNguyen = ThucHienPhepToan.Tong(5, 7);
        double ketQuaSoThuc = ThucHienPhepToan.Tong(3.5, 2.1);
        string ketQuaChuoi = ThucHienPhepToan.Tong("Xin chào, ", "thế giới!");

        Console.WriteLine($"Tổng số nguyên: {ketQuaSoNguyen}");    // Kết quả: 12
        Console.WriteLine($"Tổng số thực: {ketQuaSoThuc}");      // Kết quả: 5.6
        Console.WriteLine($"Nối chuỗi: {ketQuaChuoi}");          // Kết quả: Xin chào, thế giới!
    }
}

Cách tiếp cận này chuyển việc kiểm tra kiểu từ thời điểm biên dịch sang thời điểm chạy. Điều này có nghĩa là nếu một kiểu không hỗ trợ phép toán được gọi, lỗi sẽ xảy ra trong quá trình thực thi chứ không phải biên dịch. Do đó, người lập trình cần đảm bảo rằng các kiểu được truyền vào phương thức generic thực sự hỗ trợ các phép toán mong muốn.

3. Xây Dựng Hành Vi Động với DynamicObject

Để kiểm soát sâu hơn hành vi của các đối tượng dynamic, bạn có thể kế thừa từ lớp trừu tượng DynamicObject và ghi đè (override) các phương thức như TryInvokeMember, TryGetMember, TrySetMember, v.v. Các phương thức này cho phép bạn định nghĩa cách đối tượng phản ứng khi một phương thức được gọi, một thuộc tính được đọc hoặc ghi mà không tồn tại trong định nghĩa lớp tường minh.

using System;
using System.Dynamic;

public class BoDieuKhienDong : DynamicObject
{
    // Ghi đè phương thức này để xử lý các lời gọi phương thức động
    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        Console.WriteLine($"Đang cố gắng gọi phương thức động '{binder.Name}'...");
        // Cố gắng gọi phương thức của đối tượng cơ sở.
        // Nếu đối tượng cơ sở không có phương thức này, nó sẽ trả về false.
        bool success = base.TryInvokeMember(binder, args, out result);

        // Nếu phương thức không tồn tại, gán null cho result
        if (!success)
        {
            result = null; // Thiết lập kết quả là null nếu không tìm thấy phương thức
        }
        // Trả về true để chỉ ra rằng chúng ta đã xử lý lời gọi (ngay cả khi phương thức không tồn tại)
        // Nếu trả về false và phương thức không tồn tại, một ngoại lệ RuntimeBinderException sẽ được ném.
        return true;
    }
}

public class Vit : BoDieuKhienDong
{
    public string Keu()
    {
        return "Vịt kêu Quàng quạc!";
    }
}

public class ConNguoi : BoDieuKhienDong
{
    public string Noi()
    {
        return "Con người nói chuyện, không kêu quàng quạc.";
    }
}

public class MinhHoaDynamicObject
{
    public static void Main(string[] args)
    {
        var vitObj = new Vit();
        var nguoiObj = new ConNguoi();

        Console.WriteLine("Âm thanh của Vịt:");
        Console.WriteLine(GoiAmThanh(vitObj)); // Vit có phương thức Keu()

        Console.WriteLine("\nÂm thanh của Con Người:");
        Console.WriteLine(GoiAmThanh(nguoiObj)); // ConNguoi không có phương thức Keu()
    }

    public static string GoiAmThanh(dynamic doiTuong)
    {
        // Thử gọi phương thức Keu().
        // Đối với 'ConNguoi', phương thức này không tồn tại,
        // nhưng nhờ TryInvokeMember của BoDieuKhienDong, nó sẽ trả về null thay vì ném lỗi.
        string ketQua = doiTuong.Keu(); 
        
        // Sử dụng toán tử ?? để cung cấp giá trị mặc định nếu ketQua là null.
        return ketQua ?? "... Con người không biết kêu quàng quạc...";
    }
}

Trong ví dụ này, khi chúng ta gọi doiTuong.Keu() với đối tượng ConNguoi, phương thức Keu() không tồn tại trên lớp ConNguoi. Tuy nhiên, do ConNguoi kế thừa từ BoDieuKhienDong, lời gọi này sẽ được TryInvokeMember của BoDieuKhienDong chặn và xử lý. Vì base.TryInvokeMember không thành công, result được gán là null, và lời gọi doiTuong.Keu() sẽ trả về null.

4. Toán Tử Null-Coalescing (??)

Toán tử null-coalescing (??) là một toán tử nhị phân dùng để định nghĩa giá trị mặc định cho một kiểu dữ liệu có thể null (nullable type) hoặc kiểu tham chiếu. Cú pháp của nó là biểu_thức_trái ?? biểu_thức_phải.

Nó hoạt động như sau: nếu biểu_thức_trái không phải là null, thì toàn bộ biểu thức sẽ lấy giá trị của biểu_thức_trái. Ngược lại, nếu biểu_thức_trái là null, thì toàn bộ biểu thức sẽ lấy giá trị của biểu_thức_phải.

using System;

public class ViDuNullCoalescing
{
    public static void Main(string[] args)
    {
        string tenNguoiDung = null;
        // Nếu tenNguoiDung là null, gán "Khách" cho tenHienThi.
        // Ngược lại, tenHienThi sẽ là giá trị của tenNguoiDung.
        string tenHienThi = tenNguoiDung ?? "Khách"; 
        Console.WriteLine($"Tên hiển thị 1: {tenHienThi}"); // Kết quả: Khách

        string maSo = "A123";
        // Vì maSo không phải là null, maSoHienThi sẽ là "A123".
        string maSoHienThi = maSo ?? "Không có mã số"; 
        Console.WriteLine($"Mã số hiển thị 2: {maSoHienThi}"); // Kết quả: A123

        int? soLuong = null;
        // Nếu soLuong là null, gán 0 cho soLuongMacDinh.
        int soLuongMacDinh = soLuong ?? 0;
        Console.WriteLine($"Số lượng mặc định: {soLuongMacDinh}"); // Kết quả: 0
    }
}

Trong phần ví dụ về DynamicObject ở trên, toán tử ?? đã được sử dụng để cung cấp một thông báo mặc định khi phương thức Keu() không tồn tại trên đối tượng ConNguoi và trả về null.

Thẻ: C# Dynamic ExpandoObject DynamicObject NullCoalescing

Đăng vào ngày 4 tháng 11 lúc 23:01