ジェネリッククラスの具象型に基づく条件付き動作
-
03-07-2019 - |
質問
昨日からの私の質問はおそらく完全に明確ではなかったので、答えは得られませんでした欲しかったので、もっと一般的な方法で定式化しようとします:
明示的な条件付きステートメントを使用するか、何らかの特殊化を使用して、インスタンス化されたジェネリック型の実際の型に基づいて特別な動作を実装する方法はありますか?擬似コード:
TGenericType <T> = class
function Func : Integer;
end;
...
function TGenericType <T>.Func : Integer;
begin
if (T = String) then Exit (0);
if (T is class) then Exit (1);
end;
...
function TGenericType <T : class>.Func : Integer;
begin
Result := 1;
end;
function TGenericType <String>.Func : Integer;
begin
Result := 0;
end;
解決
TypeInfo(T)= TypeInfo(string)
を使用して、RTTIにフォールバックできます。何かがクラスかどうかをテストするには、 PTypeInfo(TypeInfo(T))^。Kind = tkClass
のようなものを使用できます。
PTypeInfo
タイプおよび tkClass
列挙メンバーは、 TypInfo
ユニットで定義されます。
他のヒント
「文字列の特別な扱いで最悪のサイズ」をどのように実装したか誰かが興味を持っている場合
class function RTTIUtils.GetDeepSize <T> (Variable : T) : Integer;
var
StringLength : Integer;
Ptr : PInteger;
begin
if (TypeInfo (T) = TypeInfo (String)) then
begin
Ptr := @Variable;
Ptr := PInteger (Ptr^);
Dec (Ptr);
StringLength := Ptr^;
Result := StringLength * SizeOf (Char) + 12;
end
else
Result := 0;
end;
私にとって、これは手元にあります。すべての貢献者に感謝します!
C#では、 typeof(T)
を実行できます。これにより、次のようなことができます
(T = String)
または
(T is class)
他の質問を見たことはありません(リンクしていません)が、本当に探しているものは何ですか? 一般に、あなたがしているようなifやスイッチを介してタイプやタイプコードを条件とする何かを行うことは、一般的には、コンテキストによってカスタマイズされたインターフェースまたは抽象関数をどこかに持つことに最も良く変換されます。
TypeInfo(T)は正しい方法です。さらに、TTypeDataレコードのようなTypInfoユニットのすべてのものを使用して、ジェネリックではなく使用するタイプの特定のプロパティを決定できます。 Tの代わりに使用されている現在の型を決定するとき、ポインタートリックを使用して変数の値を取得できます。
列挙型を汎用として受け入れるサンプルコードを次に示します。通常の列挙に対してのみ機能することに注意してください(
TEnumWontWork =(最初の= 1、2番目、3番目)
)および列挙型は、プロシージャ内でローカル型として宣言しないでください。これらの場合、コンパイラーは列挙型のTypeInfoを生成しません。
type
// Sample generic class that accepts any enumeration type as T
TEnumArr<T> = class
strict private
fArr: array of Byte;
fIdxType: TOrdType;
function IdxToInt(idx: T): Int64;
procedure Put(idx: T; Val: Byte);
function Get(idx: T): Byte;
public
constructor Create;
property Items[Index: T]: Byte read Get write Put; default;
end;
constructor TEnumArr<T>.Create;
var
pti: PTypeInfo;
ptd: PTypeData;
begin
pti := TypeInfo(T);
if pti = nil then
Error('no type info');
// Perform run-time type check
if pti^.Kind <> tkEnumeration then
Error('not an enum');
// Reach for TTypeData record that goes right after TTypeInfo record
// Note that SizeOf(pti.Name) won't work here
ptd := PTypeData(PByte(pti) + SizeOf(pti.Kind) + (Length(pti.Name)+1)*SizeOf(AnsiChar));
// Init internal array with the max value of enumeration
SetLength(fArr, ptd.MaxValue);
// Save ordinal type of the enum
fIdxType := ptd.OrdType;
end;
// Converts index given as enumeration item to integer.
// We can't just typecast here like Int64(idx) because of compiler restrictions so
// use pointer tricks. We also check for the ordinal type of idx as it may vary
// depending on compiler options and number of items in enumeration.
function TEnumArr<T>.IdxToInt(idx: T): Int64;
var
p: Pointer;
begin
p := @idx;
case fIdxType of
otSByte: Result := PShortInt(p)^;
otUByte: Result := PByte(p)^;
otSWord: Result := PSmallInt(p)^;
otUWord: Result := PWord(p)^;
otSLong: Result := PLongInt(p)^;
otULong: Result := PLongWord(p)^;
end;
end;
function TEnumArr<T>.Get(idx: T): Byte;
begin
Result := fArr[IdxToInt(idx)];
end;
procedure TEnumArr<T>.Put(idx: T; Val: Byte);
begin
fArr[IdxToInt(idx)] := Val;
end;
使用例:
type
TEnum = (enOne, enTwo, enThree);
var
tst: TEnumArr<TEnum>;
begin
tst := TEnumArr<TEnum>.Create;
tst[enTwo] := $FF;
Log(tst[enTwo]);
履歴書として、ここで3つのトリックを使用しました:
1)Tの一般的な小道具でTのTypeInfoを取得する
2)Tの詳細な小道具でTのTypeDataを取得する
3)ポインターマジックを使用して、T型として指定されたパラメーターの値を取得します。
このヘルプをお待ちしています。