Delphi 2010 (Weaver) ve TValue

Hayatimiz boyunca kim bilir kac defa Showmessage komutunu kullandik ve kim bilir kac defa IntToStr , DateToStr , TimeToStr gibi komutlari kullanarak gostermek istedigimiz degeri string tipine donusturup islemi gerceklestirdik. Bu durumdan kurtulmanin elbet bazi yollari mevcuttu , showmessage komutunu overload etmek , variant tipini kullanmak vs. Bunlardan kimisi delphi icinde standart olmadigindan astari yuzunden pahali olan cozumlerdi kimiside yavasliklari nedeniyle tercih edilmiyordu ve bizde en basit islemlerde bile tip cevrimlerini manuel yaparak yolumuza devam etmek zorunda kaliyorduk.

Overload ifadesi gecmisken asagidaki yaziya da goz atmanizi tavsiye ederim.
http://tr.delphipeak.com/2007/08/30/overload-metodlar.htm

Delphi 2010 ile birlikte genisletilmis bir RTTI (Run-Time Type Information) destegi bizi bekliyor. Tabi bicok seyde oldugu gibi bu ozelliginde bir iyi tarafi var bir de kotu tarafi. Kotu tarafi kodlarinizin decompile edilmesi eskiye nazaran biraz daha kolay olacak iyi tarafi ise calisma zamaninda nesneler hakkinda artik daha fazla bilgi edinebilecegiz ve bu da bize normalde yapilmasi mumkun olmayan ya da bircok taklalar atmamiza neden olacak isleri artik cok kolay bir sekilde yapabilmemizi saglayacak. Kotu tarafi ile ilgili olarak soyleyebilecegimiz iyi haber ise delphinin bu ozelliginin istege bagli olmasi. Yani istersek bu ozelligi kapatabiliyoruz.

{$RTTI EXPLICIT METHODS([]) FIELDS([]) PROPERTIES([])}

Bu ozellik sayesinde yapabileceklerimiz oldukca genis ama bir iki tane ornek vermek gerekirse en basitindan AOP (Aspect Oriented Programming ) uygulamak isterseniz bu cok basit bir sekilde yapabilirsiniz artik ya da tipini bilmediginiz bir degiskeni bir metoda gonul rahatligi ile gonderebilirsiniz. Tip cevrimleriyle ugrasmak yerine degiskenin tipini delphi calisma zamaninda kendi belirlesin.

Simdi bu degiskenin degerinin calisma zamaninda nasil belirlendigine soyle bir bakalim.

TValue adinda Record olarak tanimlanmis yeni bir yapimiz mevcut Delphi 2010 da. TValue kendisine gonderdigimiz degiskenin tipini calisma zamaninda belirleme gibi ozellikleri icerisinde barindiriyor.

function TValue.ToString: string;
begin
  if IsEmpty then
    Exit('(empty)'); // do not localize

  case FData.FTypeInfo^.Kind of
    tkUnknown: Result := '(unknown)'; // do not localize
    tkInteger: Result := IntToStr(AsInt64);
    tkChar: Result := string(AsType);
    tkEnumeration: Result := GetEnumName(FData.FTypeInfo, FData.FAsSLong);
    tkFloat:
      case GetTypeData(FData.FTypeInfo)^.FloatType of
        ftSingle: Result := FloatToStr(FData.FAsSingle);
        ftDouble: Result := FloatToStr(FData.FAsDouble);
        ftExtended: Result := FloatToStr(FData.FAsExtended);
        ftComp: Result := IntToStr(FData.FAsSInt64);
        ftCurr: Result := CurrToStr(FData.FAsCurr);
      end;
    tkString, tkLString, tkUString, tkWString: Result := AsType;
    tkSet: Result := SetToString(FData.FTypeInfo, FData.FAsSLong, True);
    tkClass:
      if FData.FAsObject = nil then
        Result := '(empty)' // do not localize
      else
        Result := Format('(%s @ %p)', [AsObject.ClassName, Pointer(AsObject)]);

    tkMethod: Result := Format('(method code=%p, data=%p)', [FData.FAsMethod.Code, FData.FAsMethod.Data]); // do not localize
    tkWChar: Result := AsType;
    tkVariant: Result := '(variant)'; // do not localize
    tkArray: Result := '(array)'; // do not localize
    tkRecord: Result := '(record)'; // do not localize
    tkPointer: Result := Format('(pointer @ %p)', [Pointer(FData.FAsSLong)]); // do not localize
    tkInterface: Result := Format('(interface @ %p)', [Pointer(FData.FHeapData)]); // do not localize
    tkInt64:
      with GetTypeData(FData.FTypeInfo)^ do
        if MinInt64Value > MaxInt64Value then
          Result := UIntToStr(FData.FAsUInt64)
        else
          Result := IntToStr(FData.FAsSInt64);
    tkDynArray: Result := '(dynamic array)'; // do not localize
    tkClassRef:
      if FData.FAsClass = nil then
        Result := '(empty)' // do not localize
      else
        Result := Format('(class ''%s'' @ %p)', [FData.FAsClass.ClassName, Pointer(FData.FAsClass)]); // do not localize
  end;
end;

Eger bir metod TValue tipinde bir parametre aliyorsa biz bu metoda string , integer , obje , tarih gibi bir cok degeri tip cevrimine gerek kalmadan gonderebiliyoruz artik ve gonderdigimiz parametrenin tipini TValue kendisi belirliyor, uygun olan sekilde geriye degerini donderiyor. Kucuk bir ornek yapalim.

  function DegerYaz(Value: TValue): string;
  begin
    Result := Value.ToString;
  end;

seklinde bir metodumuz olsun. Aldigi parametreye dikkat ederseniz TValue tipinde. Bu metoda gonderecegimiz her turlu veri tipi gecerlidir. ToString metodu ise bize gonderdigimiz veri tipine uygun olan string ifadeyi tespit edip geri donderecektir.

program Project2;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes,
  Rtti; // --> TValue recordunun tanimlandigi unit.

function DegerYaz(Value: TValue): string;
begin
  result := Value.ToString;
end;

var
  SL: TStringList;
  Obje: TObject;
  Dizi: array of string;
  tarih: TDate;

begin
  try
    tarih := now;
    SL := TStringList.Create;
    SL.Add('1');
    SL.Add('2');
    SL.Add('3');
    Obje := TObject.Create;
    SetLength(Dizi, 2);
    Dizi[0] := 'delphi';
    Dizi[0] := '2010';
    Dizi[0] := 'super';
    tarih := tarih + 1;
    writeln(DegerYaz('string ifade'));
    writeln(DegerYaz(1));
    writeln(DegerYaz(True));
    writeln(DegerYaz(SL));
    writeln(DegerYaz(Obje));
    writeln(DegerYaz(tarih));
    readln;
  except
    on E: Exception do
      writeln(E.ClassName, ': ', E.message);
  end;

end.

bu programin consola ciktisi ise su sekilde olacaktir.

string ifade
1
True
(TStringList @ 00A4DB00)
(TObject @ 00A30D30)
40036,9031108912)

Kod yazma aliskanligizi koklu bir sekilde degistirmeye aday olan TValue cok yakinda aramizda olacak :)