2015-10-16 12 views
4

我希望能夠檢查,添加和刪除ST:TElementSet中的T:TElements。如何使用RTTI檢查或更改哪些設置元素存在?

type 
    TElements = (elA, elB, elC); 
    TElementSet = set of TElements; 

    TMyClass<T, ST> = class 
    property SetValue:ST; 
    end; 

仿製藥並不能使我來告訴編譯器T是一個枚舉類型和ST是一組T.

RTTI使我能夠識別類型爲tkEnumeration和tkSet - 但我我不確定我是否可以使用RTTI在兩者之間建立嚴格的聯繫。這並不重要,因爲我只需要通過序數值來調整設置位。

問題是:我可以安全地做到這一點,使用泛型和RTTI,如果是這樣 - 如何?

將讚賞對現有技術的示例和/或參考。

+0

您是否嘗試過使用標準的'包括()'和'排除()'功能,以及'in'操作?只要'T'和'ST'是兼容類型,我期望它可以工作,並且不能編譯爲不兼容的類型。 –

+0

@Remy這不適用於泛型類型,因爲無法應用合適的約束 –

+0

@Lars您的屬性聲明無效。你之前在Google+上做過這個。你能修復它嗎? –

回答

5

假設我們只處理不連續的枚舉(因爲其他人沒有正確的typeinfo並且不能很容易地處理),我們可以簡單地在沒有typeInfo/RTTI的情況下完成。

enum將它設置爲enum中元素的位掩碼。

因此,例如,集合[ELA,ELC]等於00000101(從右到左),其等於5.

位來設置的索引等於枚舉+ 1的順序值(因爲第一枚枚舉值有序0,但它是第一位)。

由於不能設置單獨的比特在Delphi但僅字節我們需要計算正確的值,這導致此代碼爲包括:

集[枚舉的div 8]:=設定[枚舉DIV 8]或( 1 shl(enum mod 8))

由於集不能包含超過256個元素,我們也保存爲假設枚舉值始終是字節的大小。處理不從0開始枚舉將需要更多的代碼,併爲他們的最小讀數所屬類別和最大值

這裏是一些測試代碼 - 我tricksed有點用絕對的,而是你也可以使用hardcasts:

program GenericEnumSet; 

{$APPTYPE CONSOLE} 

type 
    TMyEnum = (elA, elB, elC); 
    TMySet = set of TMyEnum; 

    TEnumSet<TEnum,TSet> = record 
    value: TSet; 
    procedure Include(const value: TEnum); inline; 
    procedure Exclude(const value: TEnum); inline; 
    end; 

procedure _Include(var setValue; const enumValue); 
var 
    localEnum: Byte absolute enumValue; 
    localSet: array[0..31] of Byte absolute setValue; 
begin 
    localSet[localEnum div 8] := localSet[localEnum div 8] or (1 shl (localEnum mod 8)); 
end; 

procedure _Exclude(var setValue; const enumValue); 
var 
    localEnum: Byte absolute enumValue; 
    localSet: array[0..31] of Byte absolute setValue; 
begin 
    localSet[localEnum div 8] := localSet[localEnum div 8] and not (1 shl (localEnum mod 8)); 
end; 

procedure TEnumSet<TEnum, TSet>.Include(const value: TEnum); 
begin 
    _Include(Self.value, value); 
end; 

procedure TEnumSet<TEnum, TSet>.Exclude(const value: TEnum); 
begin 
    _Exclude(Self.value, value); 
end; 

var 
    mySet: TEnumSet<TMyEnum,TMySet>; 
    myEnum: TMyEnum; 
begin 
    mySet.value := []; 
    for myEnum := Low(TMyEnum) to High(TMyEnum) do 
    begin 
    mySet.Include(myEnum); 
    Assert(mySet.value = [Low(TMyEnum)..myEnum]); 
    end; 
    for myEnum := Low(TMyEnum) to High(TMyEnum) do 
    begin 
    mySet.Exclude(myEnum); 
    if myEnum < High(TMyEnum) then 
     Assert(mySet.value = [Succ(myEnum)..High(TMyEnum)]) 
    else 
     Assert(mySet.value = []); 
    end; 
    Readln; 
end. 

我離開實施其他方法和錯誤檢查作爲讀者的練習。

+0

假設問題的答案是「我可以安全地使用泛型和RTTI來做到這一點,如果是這樣 - 如何?」真的是「不」。 –

+1

把一些檢查代碼放在那裏,檢查typeinfo <> nil,通用參數是否有正確的typekind,枚舉的最小值和最大值,然後確保安全。 –

+0

我認爲Lars想要使用RTTI機制來操作集合。我不認爲這是可能的,並且需要訴諸於你展示的那種方法。我的理解是否正確? –

1

這不是很快,並且由於Delphi有泛型而不是模板,所以你不會得到任何編譯時安全,但我認爲這應該涵蓋運行時的所有基礎。

program GenericSetInclusion; 

{$APPTYPE CONSOLE} 

{$R *.res} 

uses 
    System.SysUtils, 
    System.TypInfo, 
    System.Rtti; 

type 
    TElm = (elFoo, elBar, elXyz); 
    TElms = set of TElm; 

    TOrd = 7..150; 
    TOrds = set of TOrd; 

type 
    SafeSet = record 
    class procedure Include<ST, T>(var s: ST; const e: T); static; 
    end; 

{ SafeSet } 

class procedure SafeSet.Include<ST, T>(var s: ST; const e: T); 
var 
    ctx: TRttiContext; 
    typ1: TRttiType; 
    typ2: TRttiType; 
    styp: TRttiSetType; 
    etyp: TRttiOrdinalType; 
    ttyp: TRttiOrdinalType; 
    tmp: set of 0..255; 
    o: 0..255; 
    i: integer; 
begin 
    ctx := TRttiContext.Create(); 
    typ1 := ctx.GetType(TypeInfo(ST)); 

    if (typ1 = nil) then 
    raise EArgumentException.Create('SafeSet<ST, T>.Include: ST has no type info'); 

    typ2 := ctx.GetType(TypeInfo(T)); 
    if (typ2 = nil) then 
    raise EArgumentException.CreateFmt('SafeSet<ST=%s, T>.Include: T has no type info (most likely due to explicit ordinality)', [typ1.Name]); 

    if (not (typ1 is TRttiSetType)) then 
    raise EArgumentException.CreateFmt('SafeSet<ST=%s, T=%s>.Include: ST is not a set type', [typ1.Name, typ2.Name]); 

    styp := TRttiSetType(typ1); 

    if (SizeOf(ST) > SizeOf(tmp)) then 
    raise EInvalidOpException.CreateFmt('SafeSet<ST=%s, T=%s>.Include: SizeOf(ST) > 8', [styp.Name, typ2.Name]); 

    etyp := styp.ElementType as TRttiOrdinalType; 

    if (not (typ2 is TRttiOrdinalType)) then 
    raise EArgumentException.CreateFmt('SafeSet<ST=%s, T=%s>.Include: T is not an ordinal type', [styp.Name, typ2.Name]); 

    ttyp := TRttiOrdinalType(typ2); 

    case ttyp.OrdType of 
    otSByte: i := PShortInt(@e)^; 
    otUByte: i := PByte(@e)^; 
    else 
    raise EInvalidOpException.CreateFmt('SafeSet<ST=%s, T=%s>.Include: SizeOf(T) > 1', [styp.Name, ttyp.Name]); 
    end; 

    if (ttyp.Handle <> styp.ElementType.Handle) then 
    begin 
    if (((etyp is TRttiEnumerationType) and (not (ttyp is TRttiEnumerationType)))) or 
     ((not (etyp is TRttiEnumerationType)) and (ttyp is TRttiEnumerationType)) then 
     raise EArgumentException.CreateFmt('SafeSet<ST=%s, T=%s>.Include: ST is not a set of T (ST is set of %s)', [styp.Name, ttyp.Name, etyp.Name]); 

    // ST is a set of integers rather than a set of enum 
    // so do bounds checking 
    if ((i < etyp.MinValue) or (i > etyp.MaxValue)) then 
     raise EArgumentException.CreateFmt('SafeSet<ST=%s, T=%s>.Include: %d is not a valid element for ST (ST is set of %s = %d..%d)', [styp.Name, ttyp.Name, i, etyp.Name, etyp.MinValue, etyp.MaxValue]); 
    end; 

    o := i; 

    FillChar(tmp, SizeOf(tmp), 0); 
    Move(s, tmp, SizeOf(ST)); 

    System.Include(tmp, o); 

    Move(tmp, s, SizeOf(ST)); 
end; 

procedure Test(const p: TProc); 
begin 
    try 
    p(); 
    WriteLn('Success'); 
    except 
    on E: Exception do 
     Writeln(E.ClassName, ': ', E.Message); 
    end; 
end; 

var 
    s: TElms; 
    o: TOrds; 
begin 
    Test(
    procedure 
    begin 
     SafeSet.Include(s, elFoo); 
     Assert(elFoo in s, 'elFoo not in s'); 
     Assert((s - [elFoo]) = [], 's contains elements it should not'); 

     SafeSet.Include(s, elBar); 
     Assert(elFoo in s, 'elFoo not in s'); 
     Assert(elBar in s, 'elBar not in s'); 
     Assert((s - [elFoo, elBar]) = [], 's contains elements it should not'); 

     SafeSet.Include(s, elXyz); 
     Assert(elFoo in s, 'elFoo not in s'); 
     Assert(elBar in s, 'elBar not in s'); 
     Assert(elXyz in s, 'elXyz not in s'); 
     Assert((s - [elFoo, elBar, elXyz]) = [], 's contains elements it should not'); 
    end 
); 

    Test(
    procedure 
    begin 
     SafeSet.Include(o, 7); 
     Assert(7 in o, '7 not in o'); 
     Assert((o - [7]) = [], 'o contains elements it should not'); 
    end 
); 

    Test(
    procedure 
    begin 
     SafeSet.Include(s, 7); 
     Assert(False, '7 should not be in s'); 
    end 
); 

    Test(
    procedure 
    begin 
     SafeSet.Include(o, elFoo); 
     Assert(False, 'elFoo should not be in o'); 
    end 
); 

    Test(
    procedure 
    begin 
     SafeSet.Include(o, 1); 
     Assert(False, '1 should not be in o'); 
    end 
); 

    ReadLn; 
end. 

此輸出以下對我來說,使用D10:

Success 
Success 
EArgumentException: SafeSet<ST=TElms, T=ShortInt>.Include: ST is not a set of T (ST is set of TElm) 
EArgumentException: SafeSet<ST=TOrds, T=TElm>.Include: ST is not a set of T (ST is set of TOrd) 
EArgumentException: SafeSet<ST=TOrds, T=ShortInt>.Include: 1 is not a valid element for ST (ST is set of TOrd = 7..150) 
+0

我需要的枚舉都是基於零的,並且是連續的,所幸 - 因此我不應該得到範圍檢查問題。但驗證它實際上是正常的而不是明確的順序是個好主意。 –