2009-11-21 3 views
2

я в настоящее время использую запись передать несколько параметров результата для функции и нужно добавить еще некоторые данные, как это следующим образом:Можно ли/рекомендуется использовать TStringList внутри записи?

type 
    TItemType = (itFile, itRegistry); 

    TItemDetails = record 
    Success: Boolean; 
    ItemType: TItemType; 
    TotalCount: Integer; 
    TotalSize: Int64; 
    List: TStringList; 
    end; 

function DoSomething: TItemDetails; 

Можно ли/рекомендуется использовать TStringList внутри записи для этого конкретного случая?

я нашел на Embarcadero Developer Network класс, который позволяет объявить StringList вместо TStringList и заботится о создании и освобождении списка. Будет ли это целесообразным решением? http://cc.embarcadero.com/Item/25670

Кроме того, если это действительно работает, должен ли я вручную освободить TStringList?

ответ

4

Да, обязательно помните, что если запись выходит из области видимости, тогда она теряет ссылку на объект (если вы не добавите код в противном случае).

Я использовал это StringList пример, который вы имеете в виду также, и что прекрасно работает, чтобы иметь запись управлять время жизни TStringList. Вы можете адаптировать это к использованию. Ключ - это встроенный интерфейс, который освобождает объект, когда он выходит из области действия с записью.

Вы также можете посмотреть Allen Bauer's Nullable record example. Я включил код, но вы тоже захотите прочитать статью (и комментарии). Он использует Generics в Delphi 2009 или новее, но вы можете адаптировать его к более ранним версиям Delphi. Снова ключ - это интерфейс, но он использует другой подход.

unit Foo; 

interface 

uses Generics.Defaults, SysUtils; 

type 
    Nullable<T> = record 
    private 
    FValue: T; 
    FHasValue: IInterface; 
    function GetValue: T; 
    function GetHasValue: Boolean; 
    public 
    constructor Create(AValue: T); 
    function GetValueOrDefault: T; overload; 
    function GetValueOrDefault(Default: T): T; overload; 
    property HasValue: Boolean read GetHasValue; 
    property Value: T read GetValue; 

    class operator NotEqual(ALeft, ARight: Nullable<T>): Boolean; 
    class operator Equal(ALeft, ARight: Nullable<T>): Boolean; 

    class operator Implicit(Value: Nullable<T>): T; 
    class operator Implicit(Value: T): Nullable<T>; 
    class operator Explicit(Value: Nullable<T>): T; 
    end; 

procedure SetFlagInterface(var Intf: IInterface); 

implementation 

function NopAddref(inst: Pointer): Integer; stdcall; 
begin 
    Result := -1; 
end; 

function NopRelease(inst: Pointer): Integer; stdcall; 
begin 
    Result := -1; 
end; 

function NopQueryInterface(inst: Pointer; const IID: TGUID; out Obj): HResult; stdcall; 
begin 
    Result := E_NOINTERFACE; 
end; 

const 
    FlagInterfaceVTable: array[0..2] of Pointer = 
    (
    @NopQueryInterface, 
    @NopAddref, 
    @NopRelease 
); 

    FlagInterfaceInstance: Pointer = @FlagInterfaceVTable; 

procedure SetFlatInterface(var Intf: IInterface); 
begin 
    Intf := IInterface(@FlagInterfaceInstance); 
end; 

{ Nullable<T> } 

constructor Nullable<T>.Create(AValue: T); 
begin 
    FValue := AValue; 
    SetFlagInterface(FHasValue); 
end; 

class operator Nullable<T>.Equal(ALeft, ARight: Nullable<T>): Boolean; 
var 
    Comparer: IEqualityComparer<T>; 
begin 
    if ALeft.HasValue and ARight.HasValue then 
    begin 
    Comparer := TEqualityComparer<T>.Default; 
    Result := Comparer.Equals(ALeft.Value, ARight.Value); 
    end else 
    Result := ALeft.HasValue = ARight.HasValue; 
end; 

class operator Nullable<T>.Explicit(Value: Nullable<T>): T; 
begin 
    Result := Value.Value; 
end; 

function Nullable<T>.GetHasValue: Boolean; 
begin 
    Result := FHasValue <> nil; 
end; 

function Nullable<T>.GetValue: T; 
begin 
    if not HasValue then 
    raise Exception.Create('Invalid operation, Nullable type has no value'); 
    Result := FValue; 
end; 

function Nullable<T>.GetValueOrDefault: T; 
begin 
    if HasValue then 
    Result := FValue 
    else 
    Result := Default(T); 
end; 

function Nullable<T>.GetValueOrDefault(Default: T): T; 
begin 
    if not HasValue then 
    Result := Default 
    else 
    Result := FValue; 
end; 

class operator Nullable<T>.Implicit(Value: Nullable<T>): T; 
begin 
    Result := Value.Value; 
end; 

class operator Nullable<T>.Implicit(Value: T): Nullable<T>; 
begin 
    Result := Nullable<T>.Create(Value); 
end; 

class operator Nullable<T>.NotEqual(const ALeft, ARight: Nullable<T>): Boolean; 
var 
    Comparer: IEqualityComparer<T>; 
begin 
    if ALeft.HasValue and ARight.HasValue then 
    begin 
    Comparer := TEqualityComparer<T>.Default; 
    Result := not Comparer.Equals(ALeft.Value, ARight.Value); 
    end else 
    Result := ALeft.HasValue <> ARight.HasValue; 
end; 

end. 
3

Это будет работать, но вы должны освободить его вручную. И так как записи автоматически очищаются, когда они выходят из сферы действия, и у них нет деструкторов, убедитесь, что вы делаете это правильно, может быть хлопот. Вам лучше не использовать объекты в записях. Если вам нужен тип данных, содержащий объекты, почему бы не сделать его объектом?

+0

Есть обходные пути. Интерфейс внутри записи будет управляться правильно. Вы можете перехватить уничтожение TStringList до уничтожения объекта, реализующего этот интерфейс. – gabr

+4

+1 Я часто обнаружил, что меняю записи на классы по этой причине. Вот почему я, как правило, использую записи только для структур данных, которые являются: a) очень простыми (без элементов объекта) и b) очень маловероятным для расширения (чтобы избежать более поздних рефакторингов) – jpfollenius

+0

@gabr: Суть заключается в том, что вам необходимо перезагрузить обходной путь и/или работать с этими записями по-другому со всеми вашими «нормальными» записями. _Workarounds_ - запах кода. –

-2

Почему бы не использовать что-то вроде

type PStringList = ^TStringList; 
type TMyFreakyRecord = record 
     PointerToAStringList : PStringList; 
// some more code here 
end; 

... 
var x : TMyFreakyRecord; 
    stringlist : TStringList; 
begin 
    stringList := TStringlist.create; 
    stringList.Add('any data you wish'); 
    x.PointertoaStringList := @stringlist; 
// some more code here 
end; 

и получить доступ к списку строк в записи, то как

procedure ProcedureThatPasses(AFreakyRecord: TFreakyRecord); 
var i : integer; 
begin 
    for i := 0 to AFreakyRecord.PointerToAStringList.count -1 do 
     // something with AFreakyRecord.PointerToAStringList[i]; 
end; 

для того, чтобы прозрачно освободить память, выделенную вы можете создать переменную TList, в котором вы добавляете каждую переменную типа TStringList, который используется внутри записи,

var frmMain : TfrmMain; 
    MyJunkList : TList; 

... 
implementation 
... 
procedure clearjunk; 
var i : integer; 
    o : TObject; 
begin 
    for i := MyJunkList.count -1 downto 0 do begin 
      o := MyJunkList[i]; 
      FreeandNil(o); 
    end; 
MyJunkList.clear; 
end; 
... 
initialization 
       MyJunkList := TList.Create; 
finalization 
      clearjunk; 
      FreeAndNil(MyJunkList); 
end. // end of unit 

, если это помогает, не стесняйтесь посетить http://delphigeist.blogspot.com/

+2

Я не знаю, полезен ли этот вид рекламы (ссылка на ваш блог после всех ваших ответов). Просто поместите ссылку в свой профиль и перестаньте добавлять к своим ответам шум. – jpfollenius

+0

Хотя компилятор достаточно умен, чтобы справиться с этим, я предпочитаю правильно дешифровать указатель (AFreakyRecord.PointerToAStringList^.count вместо AFreakyRecord.PointerToAStringList.count) – Remko

+0

Вопрос в том, возможно ли то, что он пытался/целесообразно. Нет никакой выгоды в принятии более сложного и обходного подхода. Объект уже ссылается указателем - зачем создавать указатель на указатель? –

1

Любое решение для правильной записи данных, управляющее строковым списком, будет включать в себя интерфейс так или иначе. Итак, почему бы не вернуть интерфейс из вашей функции в первую очередь? Добавьте свойства к интерфейсу, и для кода потребления он будет выглядеть как поля записи. Это позволит вам легко добавить дополнительные «поля записи» позже, и вы можете поставить произвольно сложный код в геттеры, которые возвращают значения.

0

Другой вопрос, чтобы быть в курсе, если вы используете SizeOf для определения объема памяти записи, она будет включать в себя только размер указателя на TStringList. Если вы попытаетесь течь это указатель, который хранится НЕ будет доступен для последующих случаев, так что вы должны игнорировать указатель на нагрузке и имеют другой метод для загрузки TStringList.

Например:

Procedure SaveRecToStream(Rec: TItemDetails ; Stream:tStream); 
var 
    i : integer; 
begin 
    Stream.Write(Rec,SizeOf(Rec)-SizeOf(tSTringList)); 
    Rec.List.saveToStream(Stream); 
end; 

Procedure LoadRecFromStream(Rec: TItemDetails ; Stream:tStream); 
var 
    i : integer; 
begin 
    FillMemory(@Rec,SizeOf(Rec),0); 
    i := Stream.Read(rec,SizeOf(Rec)-SizeOf(tStringList)); 
    if i <> SizeOf(Rec)-SizeOf(tStringList) then 
    Raise Exception.create('Unable to load record'); 
    Rec.List := tStringlist.create; 
    Rec.List.LoadFromStream(Stream); 
end; 

Это предполагает, что каждый поток содержит ровно одну запись, и что запись с переменной передается LoadRecFromStream не содержит живой TStringList (если он ранее был использован он должен быть освобожден до происходит вызов или утечка).

Смежные вопросы