Delphi 2009 에서 Generic이 추가되면서 유용한 클래스들이 추가되었다.
특히나 특정 Key와 쌍을 이루는 값을 저장할때 TDictionary는 참 유용하게 쓰인다.
일반 데이터 형을 보관할 때는 상관이 없으나 Class를 보관할때는 메모리 해제에 주의해야 한다.

uses
  Generics.Collections;

type
  TTempKey = class
  end;

  TTempValue = class
  end;

1. TDictionary
procedure TForm2.Button1Click(Sender: TObject);
var
  Dic: TDictionary<Integer,TTempValue>;
  Key: Integer;
begin
  Dic := TDictionary<Integer,TTempValue>.Create;
  try
    Dic.Add( 1, TTempValue.Create );

    for Key in Dic.Keys do
      Dic.Items[Key].Free;
  finally
    Dic.Free;
  end;
end;

Key와 매칭되는 Value를 저장할 수 있다.
여기서는 Key의 형을 Integer, Value의 형은 TTempValue 클래스로 설정했다.

Dic만 해제하면 TDictonary의 Values에 저장된 TTempValue 인스턴스가 붕 떠서 Memory Leak이 발생한다.
반드시 Dic를 해제하기 전에 Dic의 아이템들을 해제해야 한다.


2. TObjectDictionary

예전부터 Contnrs 유닛에 있던 TObjectList와 비슷하다.
생성시에 OwnsObject를 True로 설정해서 자동으로 아이템의 메모리를 해제했던 기억이 있을 것이다.

(1) Key와 Value 가 모두 Class형인 경우

procedure TForm2.Button2Click(Sender: TObject);
var
  Dic: TObjectDictionary<TTempKey,TTempValue>;
begin
  Dic := TObjectDictionary<TTempKey,TTempValue>.Create( [doOwnsKeys, doOwnsValues] );

  try
    Dic.Add( TTempKey.Create, TTempValue.Create );
  finally
    Dic.Free;
  end;
end;

인스턴스 생성시에 생성자의 인자를 보면 TDictionaryOwnerships라는 집합형을 넘긴다.
TObjectList의 OwnsObject를 생각하면 된다.
Key와 Value가 모두 클래스 형이므로 둘다 소유하는 것으로 설정했다.


(2) Value만 Class형인 경우

procedure TForm2.Button3Click(Sender: TObject);
var
  Dic: TObjectDictionary<Integer,TTempValue>;
begin
  Dic := TObjectDictionary<Integer,TTempValue>.Create( [doOwnsValues] );

  try
    Dic.Add( 1, TTempValue.Create );
  finally
    Dic.Free;
  end;
end;

Value만 클래스 형인 경우 Key를 소유하라고 doOwnsKeys를 설정하면 익셉션이 발생한다.
익셉션 클래스는 EInvalidCast, 메시지는 Invalid class typecast.


Delphi 2009부터 추가된 Generic이 있다.
간단하게 말하자면 C++의 Template라고 생각하면 되겠다.
이제 자바나 C++ 포팅할때도 충분히 쉽게 포팅할수 있게 되었다.
Generics.Collections를 보면 List 등의 클래스를 쉽게 포팅할수 있는 좋은 클래스가 많이 있다.

자바쪽 소스를 포팅하다보니 클래스에 ToArray라는 함수가 있는데 열어봐도 없길래 Generics.Collections 유닛을 열어보니 주석처리가 되어있다.
이유는 pending compiler support 라고 씌여있다.

흠... 없다면 상속해서 만들어주면 되겠군.
TObject를 담을 때 유용한 TObjectList를 상속받아서 하나 만들어보자.

type
  TMyObjectList<T: class> = class( TObjectList<T> )
  public
    function ToArray: TArray<T>;
    class procedure FreeArray( AMyArray: TArray<T> );
  end;

implementation

class procedure TMyObjectList<T>.FreeArray(AMyArray: TArray<T>);
var
  I: Integer;
begin
  for I := 0 to Length(AMyArray) - 1 do
    FreeAndNil( AMyArray[I] );

  SetLength( AMyArray, 0 );
end;

function TMyObjectList<T>.ToArray: TArray<T>;
var
  i: Integer;
begin
  SetLength(Result, Count);
  for i := 0 to Count - 1 do
    Result[i] := Items[i];
end;



type
  TMyItem = class
  public
    Data: string;
  end;

List에 담을 Item 클래스를 하나 선언하고 테스트 코드를 넣어봤다.

자동 해제 옵션을 켜고 사용할때
procedure TForm2.Button2Click(Sender: TObject);
var
  List: TMyObjectList<TMyItem>;
  Item: TMyItem;

  ItemArray: TArray<TMyItem>;
  I: Integer;
begin
  List := TMyObjectList<TMyItem>.Create;

  Item := TMyItem.Create;
  Item.Data := 'first item';
  List.Add(Item);

  Item := TMyItem.Create;
  Item.Data := 'second item';
  List.Add(Item);

  ItemArray := List.ToArray;

  for I := 0 to Length(ItemArray) - 1 do
    ShowMessage(ItemArray[I].Data);

  // 주의! 리스트가 해제될때 아이템도 같이 해제된다.
  // Free한 이후에 ItemArray는 사용이 불가하다.
  List.Free;
end;

자동 해제 옵션을 끄고 사용할때
procedure TForm2.Button1Click(Sender: TObject);
var
  List: TMyObjectList<TMyItem>;
  Item: TMyItem;

  ItemArray: TArray<TMyItem>;
  I: Integer;
begin
  List := TMyObjectList<TMyItem>.Create(False);

  Item := TMyItem.Create;
  Item.Data := 'first item';
  List.Add(Item);

  Item := TMyItem.Create;
  Item.Data := 'second item';
  List.Add(Item);

  ItemArray := List.ToArray;

  // 생성할 때 AOwnsObjects 옵션을 False로 설정했기 때문에 List를 해제하더라도 Item은 해제되지 않는다.
  List.Free;

  for I := 0 to Length(ItemArray) - 1 do
    ShowMessage(ItemArray[I].Data);

  // Array의 사용을 마치면 배열을 해제하여 Item을 해제한다.
  // 해제하기 전까지는 ItemArray를 계속 이용할 수 있다.
  TMyObjectList<TMyItem>.FreeArray( ItemArray );
end;


예전에는 TObjectList를 이용하더라도 Item을 형변환해서 사용하거나 상속받아서 새로 만들고 했었는데 Generic을 이용하니 너무나 편하다.
C++의 Template가 이제 가물가물하니 시간내서 Delphi와 비교 좀 해봐야겠다.

+ Recent posts