1. Cấu Trúc Dữ Liệu Của TValue
Lớp TValue được khai báo trong đơn vị hệ thống System.Rtti.pas. Đây là công cụ chính để thao tác với dữ liệu phản chiếu (RTTI) tại thời gian chạy.
Có ba phương thức cốt lõi cần lưu ý:
Make(...): Thực hiện đóng gói bất kỳ loại dữ liệu nào vào một phiên bảnTValue.ExtractRawData(...)vàExtractRawDataNoCopy(...): Dùng để giải nén dữ liệu từTValuera kiểu mục tiêu. Sự khác biệt nằm ở việc quản lý bộ nhớ:ExtractRawDataNoCopycó thể trả về con trỏ đến dữ liệu trên Heap, trong khiExtractRawDatađảm bảo an toàn hơn bằng cách sao chép dữ liệu.GetReferenceToRawData: Cung cấp trực tiếp con trỏ trỏ đến vùng nhớ chứa giá trị thực tế.
2. Chuyển Đổi Kiểu Dữ Liệu Sang TValue
Dưới đây là ví dụ minh họa việc tạo đối tượng TValue từ các kiểu nguyên thủy và cấu trúc (Record).
program DemoChuyenDoiCoBan;
{$APPTYPE CONSOLE}
uses SysUtils, Windows, TypInfo, Rtti;
var
bienSoLe : Integer;
tGiaTriSo : TValue;
toaDo : TRect;
tGiaTriKhoi : TValue;
begin
bienSoLe := 1234;
// Sử dụng Make để chuyển đổi kiểu Integer
TValue.Make(@bienSoLe, TypeInfo(Integer), tGiaTriSo);
WriteLn('Giá trị số:', tGiaTriSo.ToString);
toaDo.Left := 10;
toaDo.Right := 20;
// Đóng gói cấu trúc TRect vào TValue
TValue.Make(@toaDo, TypeInfo(TRect), tGiaTriKhoi);
WriteLn('Thông tin khối:', tGiaTriKhoi.ToString);
Readln;
end.
3. Truy Xuất Lại Kiểu Dữ Liệu Từ TValue
Khi tiến hành phản trình tự hóa (deserialization), nếu xác định được đích đến, ta có thể khởi tạo một mẫu rỗng cho TValue như sau:
TValue.Make(nil, TypeInfo(TargetType), OutputValue);
Sau đó dùng ExtractRawData để gán ngược lại dữ liệu về biến thường:
program DemocGiaiMa;
{$APPTYPE CONSOLE}
uses SysUtils, Windows, TypInfo, Rtti;
var
nguonDuLieu : TRect;
kqTrichXuat : TRect;
doiTuong : TValue;
begin
nguonDuLieu.Left := 15;
nguonDuLieu.Right := 25;
// Bước 1: Biến thường thành TValue
TValue.Make(@nguonDuLieu, TypeInfo(TRect), doiTuong);
// Bước 2: TValue quay lại biến thường
doiTuong.ExtractRawData(@kqTrichXuat);
WriteLn('Left:', kqTrichXuat.Left);
WriteLn('Right:', kqTrichXuat.Right);
Readln;
end.
4. Thao Tác Thành Viên Qua Pointer
Với một đối tượng đã được bọc bởi TValue, ta có thể dùng GetReferenceToRawData để lấy địa chỉ tham chiếu. Kết hợp với TRttiContext, ta có thể thay đổi giá trị các trường (field) mà không cần biết tên biến cụ thể tại thời điểm biên dịch.
program DemoCanhCapThanhVien;
{$APPTYPE CONSOLE}
uses SysUtils, Windows, TypInfo, Rtti;
var
cauTrucMau : TRect;
giaTriChung : TValue;
moTaLoai : TRttiContext;
begin
moTaLoai := TRttiContext.Create;
try
// Tạo TValue rỗng tương ứng với TRect
TValue.Make(nil, TypeInfo(TRect), giaTriChung);
// Gán giá trị trực tiếp vào vùng nhớ thông qua RTTI
moTaLoai.GetType(TypeInfo(TRect)).GetField('Left').SetValue(giaTriChung.GetReferenceToRawData, 100);
moTaLoai.GetType(TypeInfo(TRect)).GetField('Right').SetValue(giaTriChung.GetReferenceToRawData, 200);
// Xuất kết quả để kiểm chứng
giaTriChung.ExtractRawData(@cauTrucMau);
WriteLn(cauTrucMau.Left);
WriteLn(cauTrucMau.Right);
finally
moTaLoai.Free;
end;
Readln;
end.
5. Các Hàm Tiện Ích Generic
Định ngôn ngữ Delphi cung cấp các hàm mở rộng giúp quá trình chuyển đổi trở nên ngắn gọn hơn so với việc gọi thủ tục Make thủ công:
From<T>: Tự động suy luận và đóng gói dữ liệu.AsType<T>: Ép kiểu ngược lại.IsType<T>/TryAsType<T>: Kiểm tra tính tương thích kiểu trước khi ép buộc.
program DemoGenericHuuIch;
{$APPTYPE CONSOLE}
uses SysUtils, Windows, TypInfo, Rtti;
var
diaChi : TRect;
ketQua : TRect;
baoVon : TValue;
begin
diaChi.Left := 5;
diaChi.Right := 15;
// Cách viết ngắn gọn hơn nhiều
baoVon := TValue.From<TRect>(diaChi);
WriteLn('Kiểu khớp?', baoVon.IsType<TRect>);
ketQua := baoVon.AsType<TRect>;
WriteLn(ketQua.Left);
WriteLn(ketQua.Right);
Readln;
end.
6. Xử Lý Mảng Động
Nếu đối tượng TValue được khởi tạo từ một mảng, ta cần sử dụng nhóm phương thức chuyên biệt để duyệt hoặc chỉnh sửa phần tử:
GetArrayLength: Lấy độ dài mảng.GetArrayElement(Index): Lấy giá trị tại chỉ số.SetArrayElement(Index, Value): Ghi đè giá trị.
Bạn có thể định nghĩa mảng động thông qua alias:
type
TMangSo = array of Integer;
// Hoặc sử dụng generice sẵn có trong System.pas
// IntArr : TArray<Integer>;
7. Lưu Ý Về Việc Chuyển Đổi Variant
Đây là khu vực dễ gây nhầm lẫn. Hàm TValue.FromVariant thực chất sẽ giải mã nội dung bên trong Variant chứ không giữ nguyên định dạng Variant.
program DemoFromVariant;
{$APPTYPE CONSOLE}
uses SysUtils, TypInfo, Rtti;
var
bienLinhDong : Variant;
doiTuongKyHuu : TValue;
begin
bienLinhDong := 'Delphi Text';
doiTuongKyHuu := TValue.FromVariant(bienLinhDong);
// Kết quả Kind sẽ là TkString, không phải TkVariant
WriteLn('Loại:', GetEnumName(TypeInfo(TTypeKind), Ord(doiTuongKyHuu.Kind)));
WriteLn('Nội dung:', doiTuongKyHuu.ToString);
Readln;
end.
Nếu bạn muốn giữ nguyên cấu trúc Variant bên trong TValue, hãy sử dụng cú pháp Generic:TValue.From<Variant>:
program DemoGiurnamVariant;
{$APPTYPE CONSOLE}
uses SysUtils, TypInfo, Rtti;
var
bienLinhDong : Variant;
doiTuongKyHuu : TValue;
begin
bienLinhDong := 'Hello World';
// Giữ nguyên kiểu Variant
doiTuongKyHuu := TValue.From<Variant>(bienLinhDong);
WriteLn('Loại:', GetEnumName(TypeInfo(TTypeKind), Ord(doiTuongKyHuu.Kind)));
WriteLn('Nội dung:', doiTuongKyHuu.AsType<Variant>);
Readln;
end.