2012-01-19 21 views
11

Tôi có một lớp chung Delphi cho thấy một hàm với một đối số của kiểu generic. Bên trong hàm này, tôi cần phải chuyển một thể hiện của kiểu generic vào một đối tượng khác đang chờ kiểu biến thể. Tương tự như vậy:Làm thế nào tôi có thể chuyển đổi từ chung sang biến thể trong Delphi

type 
    IMyInterface = interface 
    DoStuff(Value: Variant); 
    end;  

    TMyClass<T> = class 
    FMyIntf: IMyInterface 
    procedure DoStuff(SomeValue: T); 
    end; 

[...] 

procedure MyClass<T>.DoStuff(SomeValue: T); 
begin 
    FMyIntf.DoStuff((*convert SomeValue to Variant here*)); 
end; 

Tôi đã thử sử dụng Rtti.TValue.From (SomeValue) .AsVariant. Điều này làm việc cho các loại tích phân, nhưng đã làm nổ tung Booleans. Tôi không thấy lý do tại sao, vì thông thường tôi có thể gán một giá trị Boolean cho một Biến thể ...

Có cách nào tốt hơn để thực hiện chuyển đổi này không? Tôi chỉ cần nó để làm việc cho các kiểu tích hợp đơn giản (không bao gồm liệt kê và ghi)

+0

Các bạn đã cố gắng tạo ra một biến địa phương của loại 'Biến thể', gán' SomeValue' cho nó, và sau đó chuyển biến cục bộ sang 'FMyIntf.DoStuff()'? –

+0

Có. Tôi không thể làm điều đó vì không có dàn diễn viên hợp lệ từ 'T' đến 'Biến thể' ... –

Trả lời

10

Tôi nghĩ rằng không có cách nào trực tiếp để chuyển đổi loại generic thành biến thể vì biến thể không thể chứa tất cả các loại có thể. Bạn phải viết thói quen chuyển đổi cụ thể của bạn. Ví dụ:

interface 
//... 
type 
    TDemo = class 
    public 
    class function GetAsVariant<T>(const AValue: T): Variant; 
    end; 
//... 
implementation 
uses 
    Rtti, 
    TypInfo; 
//... 

{ TDemo} 

class function TDemo.GetAsVariant<T>(const AValue: T): Variant; 
var 
    val: TValue; 
    bRes: Boolean; 
begin 
    val := TValue.From<T>(AValue); 
    case val.Kind of 
    tkInteger: Result := val.AsInteger; 
    tkInt64: Result := val.AsInt64; 
    tkEnumeration: 
    begin 
     if val.TryAsType<Boolean>(bRes) then 
     Result := bRes 
     else 
     Result := val.AsOrdinal; 
    end; 
    tkFloat: Result := val.AsExtended; 
    tkString, tkChar, tkWChar, tkLString, tkWString, tkUString: 
     Result := val.AsString; 
    tkVariant: Result := val.AsVariant 
    else 
    begin 
     raise Exception.Create('Unsupported type'); 
    end; 
    end; 
end; 

Bởi vì TValue.AsVariant xử lý hầu hết các chuyển đổi loại nội bộ, chức năng này có thể được đơn giản hóa. Tôi sẽ xử lý kiểu liệt kê trong trường hợp bạn có thể cần đến chúng sau:

class function TDemo.GetAsVariant<T>(const AValue: T): Variant; 
var 
    val: TValue; 
begin 
    val := TValue.From<T>(AValue); 
    case val.Kind of 
    tkEnumeration: 
    begin 
     if val.TypeInfo = TypeInfo(Boolean) then 
     Result := val.AsBoolean 
     else 
     Result := val.AsOrdinal; 
    end 
    else 
    begin 
     Result := val.AsVariant; 
    end; 
    end; 

có thể sử dụng:

var 
    vValue: Variant; 
begin 
    vValue := TDemo.GetAsVariant<Boolean>(True); 
    Assert(vValue = True); //now vValue is a correct Boolean 
+0

Tôi sợ nó sẽ giống như vậy :-P Sau một chút trêu chọc trong nội bộ của TValue, nó chỉ ra TypeKind của Boolean là tkEnumeration, và TValue đặt ra một ngoại lệ khi gọi AsVariant trên một giá trị có TypeKind tkEnumeration. Điều này cũng có nghĩa là ví dụ của bạn - mặc dù nó không làm tăng ngoại lệ - vẫn trả về biến thể sai, vì Boolean của tôi được chuyển đổi 'AsOrdinal', dẫn đến một biến thể của kiểu tích phân - không boolean ... Có lẽ tôi cần kiểm tra cả hai typekind và typename .... –

+0

@MathiasFalkenberg Tôi đã chỉnh sửa câu trả lời của mình để xử lý các boolean chính xác. – Linas

+0

+1. Bạn có thể tạo một GenericToVariant tối ưu bằng cách chuyển đổi nhiều loại như bạn cảm thấy như chuyển đổi, thành một số biểu diễn có thể là một biến thể. Nếu bạn muốn, bạn có thể thêm mã để chuyển đổi các kiểu lớp địa phương quan trọng (TObject), sử dụng phương thức TObject.ToString mới. –

0

Một cách khác (thử nghiệm XE10)

Var 
    old : variant; 
    val : TValue; 
Begin 
    val := TValue.FromVariant(old); 
End;