2011-12-02 8 views
11

私はイニシャライザを実装する制約のないジェネリック型のAtomicを持っています(詳細はprevious question)。制約のないジェネリック型に基づくオブジェクトインスタンスの作成

type 
    Atomic<T> = class 
    type TFactory = reference to function: T; 
    class function Initialize(var storage: T; factory: TFactory): T; 
    end; 

は、今私は(typeof演算(T)がtkClassであることを提供する)Tから型情報を取る簡素化初期化関数を記述し、デフォルトのコンストラクタを使用して新しいインスタンス(必要なとき)を作成します。

悲しいことに、これは失敗します。

class function Atomic<T>.Initialize(var storage: T): T; 
begin 
    if not assigned(PPointer(@storage)^) then begin 
    if PTypeInfo(TypeInfo(T))^.Kind <> tkClass then 
     raise Exception.Create('Atomic<T>.Initialize: Unsupported type'); 
    Result := Atomic<T>.Initialize(storage, 
     function: T 
     begin 
     Result := TClass(T).Create; // <-- E2571 
     end); 
    end; 
end; 

コンパイラがエラーE2571 Type parameter 'T' doesn't have class or interface constraintを報告します。

コンパイラをトリックしてクラスTのインスタンスを作成するにはどうすればよいですか?

答えて

5

新しいDelphiを使用することができますこの作業を行います。与えられた解の欠点は、コンストラクターの名前がCreateでない場合には機能しないことです。常に動作させる必要がある場合は、型のメソッドを列挙し、それがコンストラクタであるかどうかをチェックし、0のパラメータを持っているかどうかを確認してから呼び出します。 Delphi XEで動作します。サンプルコード:

class function TTest.CreateInstance<T>: T; 
var 
    AValue: TValue; 
    ctx: TRttiContext; 
    rType: TRttiType; 
    AMethCreate: TRttiMethod; 
    instanceType: TRttiInstanceType; 
begin 
    ctx := TRttiContext.Create; 
    rType := ctx.GetType(TypeInfo(T)); 
    AMethCreate := rType.GetMethod('Create'); 

    if Assigned(AMethCreate) and rType.IsInstance then 
    begin 
    instanceType := rType.AsInstance; 

    AValue := AMethCreate.Invoke(instanceType.MetaclassType, []);// create parameters 

    Result := AValue.AsType<T>; 
    end; 
end; 

更新ソリューション:

class function TTest.CreateInstance<T>: T; 
var 
    AValue: TValue; 
    ctx: TRttiContext; 
    rType: TRttiType; 
    AMethCreate: TRttiMethod; 
    instanceType: TRttiInstanceType; 
begin 
    ctx := TRttiContext.Create; 
    rType := ctx.GetType(TypeInfo(T)); 
    for AMethCreate in rType.GetMethods do 
    begin 
    if (AMethCreate.IsConstructor) and (Length(AMethCreate.GetParameters) = 0) then 
    begin 
     instanceType := rType.AsInstance; 

     AValue := AMethCreate.Invoke(instanceType.MetaclassType, []); 

     Result := AValue.AsType<T>; 

     Exit; 
    end; 
    end; 
end; 

そして、このようにそれを呼び出す:

var 
    obj: TTestObj; 
begin 
    obj := TTest.CreateType<TTestObj>; 
+0

ありがとうございます。しかし、XE2 Update 2の問題の中核は、Tが 'class'制約でマークされていないとTypeInfo(T)がコンパイルされないことです。 – gabr

+0

これは分かりませんでした。これは "機能"かバグですか? – Linas

+0

私は確信していませんが、私はそれが機能だと恐れています。 – gabr

13

あなたはクラス参照取得するためにGetTypeDataを使用することができます(次のリリースでは、うまくいけば)

Result := T(GetTypeData(PTypeInfo(TypeInfo(T)))^.ClassType.Create); 
のDelphi XE2では

を、あなたが行うことができます:

var 
    xInValue, xOutValue: TValue; 

xInValue := GetTypeData(PTypeInfo(TypeInfo(T)))^.ClassType.Create; 
xInValue.TryCast(TypeInfo(T), xOutValue); 
Result := xOutValue.AsType<T>; 

(これはむしろ回避方法が発見されました使用することにより、Error in OtlSync XE2cjsalamonを使用します。

+0

私が結果をTにキャストしていれば(私はあなたの例を修正しました)、私が望んだとおりに正確に動作します - ありがとう! – gabr

+0

ようこそ!はい、型キャストする必要があります。 –

+0

このコードが間違ったコンストラクタを呼び出すことが判明しました(http://www.thedelphigeek.com/2011/12/creating-object-from-unconstrained.html?showComment=1323943258780#c4856703763737125607)。 Linasの "更新されたソリューション"は、その点でより効果的です。 – gabr

0

私は右のそれを得た場合は、ジェネリック型 "Tは" クラスであります。この場合、単に宣言します。代わりにフラット

Atomic<T> = class 

Atomic< T: class > = class 

をあなたはコンストラクタとすべてを使用することができますので、これは、Tはクラス型であることをコンパイラに教えてくれます特別な回避策を持たないクラスタイプの他の機能

私の理解が間違っていた場合、私はお詫び申し上げます。

+1

いいえ、Tはときどきクラスに過ぎず、そうでない場合もあります。 – gabr

関連する問題