János's profileJános JankaPhotosBlogListsMore Tools Help

Blog


    September 28

    Observer pattern

    Miközben renoválom az egyik régebbi projektemet, gondoltam egy-két okosságot megosztok, amit érdemes lehet Delphi fejlesztőként ismerni. Pl. előfordulhatnak olyan esetek, amikor tudni szeretnénk valamiről/valakiről, hogy mi is történt vele. Esetemben például ez az autentikált felhasználót jelenti. Azt szeretném, hogy a programból bárhonnan felhasználót lehessen váltani anélkül, hogy 67 ablakon át kelljen ugrálni. Az TApplicationEvents osztállyal ugyan eltudunk kapni minden bill. leütést és ezt felhasználva autentikálni, de ez kevés. Ki fogja értesíteni az objektumokat (ablakokat), hogy itt kijelentkeztek és más jelentkezett be? Ugyanis ilyenkor a jogosultságokat is kezelni kell! Ill. amihez meg nincs is joga az adott usernek, egyszerűen bezárni (persze a mentésre is figyelni kell). Na pl. ehhez tökéletes az Observer pattern. Az ötlet egyszerű: a figyelt objektum tárolja a figyelők referenciáit, majd pedig kiértesíti őket, ha megváltozik valami/szükséges. Én implementáltam ezt a patternt generikusokkal, itt egy pl.:

    unit Observer;
    
    interface
    
    uses
      Classes;
    
    type
      IObservableObject<T> = interface;
    
      IObserver<T> = interface
        ['{7C62D715-BE5E-4BC7-AF91-56D108957114}']
        procedure ObservedObjectChanged(Value: IObservableObject<T>);
      end;
    
      IObservableObject<T> = interface
        ['{3E21EE2B-9A31-4406-B4BE-1ABB42DAA9F1}']
        procedure Attach(Observer: IObserver<T>);
        procedure Detach(Observer: IObserver<T>);
        procedure Notify;
      end;
    
      TObservableObject<T> = class(TInterfacedObject, IObservableObject<T>)
      private
        FInterfaceList: IInterfaceList;
      protected
        procedure Attach(Observer: IObserver<T>);
        procedure Detach(Observer: IObserver<T>);
        procedure Notify;
      end;
    
    implementation
    
    procedure TObservableObject<T>.Attach(Observer: IObserver<T>);
    begin
      if not Assigned(Observer) then
        Exit;
    
      if not Assigned(FInterfaceList) then
        FInterfaceList:= TInterfaceList.Create;
    
      if FInterfaceList.IndexOf(Observer) = -1 then
        FInterfaceList.Add(Observer);
    end;
    
    procedure TObservableObject<T>.Detach(Observer: IObserver<T>);
    begin
      if Assigned(Observer) and Assigned(FInterfaceList) then
      begin
        FInterfaceList.Remove(Observer);
        if FInterfaceList.Count = 0 then
          FInterfaceList:= nil;
      end;
    end;
    
    procedure TObservableObject<T>.Notify;
    var
      I: Integer;
    begin
    if Assigned(FInterfaceList) then for I:= Pred(FInterfaceList.Count) downto 0 do (FInterfaceList[I] as IObserver<T>).ObservedObjectChanged(Self);
    end; end.

    A használata egy picivel körülményesebb:

    Ez lesz a figyelt objektum:

    type
      // Mégegy dolog, ami működik C#-ban és Delphiben nem:
      //   IAuthentication = interface(IObservableObject<IAuthentication>)
      // E2086 Type 'IAuthentication' is not yet completely defined
      IAuthentication = interface 
        ['{62369856-94E1-41C5-BBCE-C3A025287040}']
        function GetEmployee: IEmployee;
        procedure SetEmployee(Value: IEmployee);
        property Employee: IEmployee read GetEmployee write SetEmployee;
      end;
    
      IObservableAuthentication = interface(IObservableObject<IAuthentication>)
        ['{2AF86F76-F708-42AA-8859-8070B1B15F6E}']
      end;
    
      TAuthentication = class(TObservableObject<IObservableAuthentication>, IAuthentication)
      private
        FEmployee: IEmployee;
        function GetEmployee: IEmployee;
        procedure SetEmployee(Value: IEmployee);
      public
        property Employee: IEmployee read FEmployee write SetEmployee;
      end;
    
    procedure TAuthentication.SetEmployee(Value: IEmployee);
    begin
      FEmployee:= Value;
      Notify;
    end;
    

    S itt a form:

    unit FormObject;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, Observer, Authentication, Employee;
    
    type
      TFoObject = class(TForm, IObserver<IAuthentication>)
      private
        procedure ObservedObjectChanged(Value: IObservableObject<IAuthentication>);
      protected
        procedure AuthenticationChanged(Authenticated: IEmployee); virtual;
      public
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
      end;
    
    implementation
    
    {$R *.dfm}
    
    procedure TFoObject.AuthenticationChanged(Authenticated: IEmployee);
    begin
      // Ide jön a kód, pl.:
    // Caption:= IfThen(Assigned(Authenticated), Authenticated.Name, '<Unknown>'); end; constructor TFoObject.Create(AOwner: TComponent); begin inherited; Authenticated.Attach(Self); end; destructor TFoObject.Destroy; begin Authenticated.Detach(Self); inherited; end; procedure TFoObject.ObservedObjectChanged( Value: IObservableObject<IAuthentication>); begin AuthenticationChanged((Value as IAuthentication).Employee) end; end.

    Innentől kezdve ebből a formból származtatva, hála a vizuális form öröklésnek, minden ablak képes kezelni a bejelentkezési változásokat.

    September 26

    C# using és lock utánzat natív kódban

    unit ObjectHelper;
    
    interface
    
    uses
      SysUtils;
    
    type
      TObjectHelper = class sealed
      public
        class procedure Lock(Obj: TObject; Proc: TProc); static;
        class procedure Using<T: class>(Obj: T; Proc: TProc<T>); static;
      end;
    
    implementation
    
    //------------------------------------------------------------------------------
    // C# lock
    //
    class procedure TObjectHelper.Lock(Obj: TObject; Proc: TProc);
    begin
      TMonitor.Enter(Obj);
      try
        Proc;
      finally
        TMonitor.Exit(Obj);
      end;
    end;
    
    //------------------------------------------------------------------------------
    // C# using
    //
    class procedure TObjectHelper.Using<T>(Obj: T; Proc: TProc<T>);
    begin
      try
        Proc(Obj);
      finally
        Obj.Free;
      end;
    end;
    
    end.

    Egy rövid kis pl. az épp most renoválás alatt álló projektemből ennek használatára:

    TObjectHelper.Using<TJPEGImage>
    ( TResources.Create<TJPEGImage>( TResources.JPG_BUSINESSMANAGEMENT),
    procedure (Jpeg: TJPEGImage) begin SrcBitmap.Assign(Jpeg); end );

    Látszik, hogy ez a módszer rövid életű objektumok létrehozásához kiválóan alkalmas, s végre el lehet felejteni a manuális Destroy/Free hívogatásokat. Persze saját objektumok esetében lehet ezt rugalmasabban, interfész alapon kezelni, ahol automatikusan megsemmisülnek az objektumok a Delphi referenciaszámlási módszerének köszönhetően. Elég kifinomult a memóriakezelési rendszere a Delphinek, s ha hozzá vesszük, hogy van ReportMemoryLeaksOnShutdown is, képtelenség memory leaket csinálni.

    September 16

    Fő a stabilitás

    Megoldódott a Vista SP1 telepítési problémám a Vista DVD-s upgraddel (hála Istennek). Igen ám, de már meg nem tudok WPF-s projekteket fordítani. Hiába teszem újra a VS SP1-et, vagy javítom a .NET 3.5-t, akkor is ezt vágja a képembe egy új üres projekt lefordításakor:

    Error 1: The "SplashScreen" parameter is not supported by the "MarkupCompilePass1" task. Verify the parameter exists on the task, and it is a settable public instance property.

    Error 2: The "MarkupCompilePass1" task could not be initialized with its input parameters.

    Sőt mi több, a VS is elszáll kilépéskor. De úgy látom, vannak mások is rajtam kívül:

    https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=361699&wa=wsignin1.0

    Az utolsó megoldást a teljes VS újratelepítése jelenti számomra. Remélem nem a Windows-t kell, mert arra mostanában biztos, hogy nem fogok sort keríteni. Már így is többször kellett újra raknom, mint az XP-t.

    Közben megszületett a megoldás: mindent le kellett szedni, amibe a visual szó benne volt :) és újratelepíteni.

    September 11

    Delphi 2009 for Win32

    Miközben a .NET erőteljesen fejlődik és a Világ egyre jobban a virtuális gépek irányába mozdul el, még mindig tudnak meglepetéseket okozni a natív fejlesztésben is. Ezt mi sem bizonyítja jobban, hogy még a Microsoft is fejleszti natív eszközeit, hiszen vannak olyan területek, ahol ezek használata megkerülhetetlen. Most a CodeGear és Embarcadero páros kihozta az első komolyabb és jelentősebb fejlesztéseken átesett natív kódú fejlesztőeszközét Delphi 2009 néven. Talán a Delphi 7 óta ez az első Delphi, ami ténylegesen sok újítást hoz a natív fejlesztésbe.

    A 14 napos próba verzió mától letölthető a CodeGear oldaláról. Néhány érdekességet azért kiemelnék, melyek szerintem egész jók, kezdve a nyelvvel: generikusok és anonymous metódusok (jegyezném meg, most natív kód compilerről beszélünk). Illetve ajánlanám Barry Kelly blogját, aki a Delphi compiler egyik fejlesztője és részletesen elmagyarázza az újdonságokat, ill. lehet kérdezni is tőle, mint pl. ahogy ezt mások már meg is tették:

    Lambdas

    Generic Methods and Type Inferencing

    TELEPÍTÉS:

    There is a known issue that will affect a small number of users, including those with Hungarian regional options, where the installer fails to start after prepartion of the InstallAware Wizard. To remedy this issue, open the Regional and Language Options in Control Panel and temporarily switch your Regional Options setting to English. After install, you can safely restore your regional setting.

    Át kell állítani a területi és nyelvi beállításokat angolra (megj.: a Windows-t nem kell újraindítani), majd a telepítést követően már visszaállíthatjuk magyarra.

    Új nyelvi fícsörök

    A generikusok megvalósítása megegyezik a .NET-es megvalósítással, csak épp itt jól is működik, ellenben a Delphi .NET-el, ahol még voltak gondok, mint azt korábban írtam. Ezen felül van nekünk két olyan unitunk, hogy Generics.Defaults és Generics.Collections. A Generics.Defaults például a következőket tartalmazza:

    IComparer<T>
    IEqualityComparer<T>
    TComparer<T>
    TEqualityComparer<T>
    TSingletonImplementation
    TDelegatedEqualityComparer<T>
    TDelegatedComparer<T>
    TCustomComparer<T>
    TStringComparer 
    

    Illetve még jónéhány segédfüggvényt, mint pl.:

    function _LookupVtableInfo(intf: TDefaultGenericInterface; info: PTypeInfo; size: Integer): Pointer;
    function BinaryCompare(const Left, Right: Pointer; Size: Integer): Integer;
    function BobJenkinsHash(const Date; Len, InitData: Integer): Integer;
    

    A Generics.Collections unit szintúgy tartalmaz néhány generikus kollekciót, mint pl.:

    TArray
    TEnumerator<T>
    TEnumerable<T>
    TList<T>
    TQueue<T>
    TStack<T>
    TPair<TKey,TValue>
    TDictionary<TKey,TValue>
    TObjectList<T>
    TObjectQueue<T>
    TObjectStack<T>
    TObjectDictionary<TKey,TValue>
    
    function InCircularRange(Bottom, Item, TopInc: Integer): Boolean;

    Ezen felül minden osztály tartalmaz egy ToString-, Equals- és egy GetHashCode virtuális tagfüggvényt, akárcsak .NET-ben ezt megszokhattuk. Minden osztály tartalmaz egy UnitName propertyt is, ami annak a unitnak a nevét adja vissza értelemszerűen, ahol a típus deklarálva lett. Meg hát ugye a régóta hiányzó kis finomságok most már elérhetők a generikus lista osztályok által:

    AddRange
    InsertRange DeleteRange
    Contains BinarySearch OnNotify esemény

    Sőt még TStringBuilder osztály is van, akárcsak .NET-ben.

    Következő érdekes kis téma:
    Névtelen metódusok (Anonymous Methods)

    program NewFeatures;
    
    {$APPTYPE CONSOLE}
    
    uses
      SysUtils;
    
    type
      TProc<T> = reference to procedure(const Value: T);
    
      TClass = class
        class procedure Call<T>(Proc: TProc<T>; const Value: T); static;
      end;
    
      class procedure TClass.Call<T>(Proc: TProc<T>; const Value: T);
      begin
        Proc(Value);
      end;
    
    procedure MyProc(const Value: string);
    begin
      WriteLn(Value);
    end;
    
    begin
      // a régi módszerrel
      TClass.Call<string>(
        MyProc, 'Unicode string'
      );
    
      // anonymous metódussal
      TClass.Call<Integer>(
        procedure(const Value: Integer)
        begin
          WriteLn(Value);
        end, 10
      );
    
      ReadLn;
    end.

    Ez a fentebbi kis példa illusztrálja miként is akar működni ez. Mellesleg jegyzem meg, az anonymous metódusok kiválóan alkalmasak szálkezelés esetén egyaránt. Pontosan ezért a thread osztály Synchronize metódusa támogatja ezt a fajta paraméterezést is:

    type
      TAThread = class(TThread)
      private
        FListBox: TListBox;
      protected
        procedure Execute; override;
      public
        constructor Create(ListBox: TListBox);
        property ListBox: TListBox read FListBox write FListBox;
      end;
    
    constructor TAThread.Create(ListBox: TListBox);
    begin
      inherited Create(True);
      FreeOnTerminate := True;
      Self.ListBox := ListBox;
      Resume;
    end;
    
    procedure TAThread.Execute;
    var
      I      : Integer;
      Numbers: TList<Integer>;
    begin
      Numbers:= TList<Integer>.Create;
      try
        for I := 1 to 1000 do
         if not Terminated then
           Numbers.Add(I)
         else
           Exit;
    
         Synchronize
         (
           procedure
           var
             N: Integer;
           begin
             ListBox.Items.BeginUpdate;
             try
               for N in Numbers do
                 ListBox.Items.Add(IntToStr(N));
             finally
               ListBox.Items.EndUpdate;
             end;
           end
         );
      finally
        Numbers.Free;
      end;
    end;
    

    Ebbe az a szép, hogy nem kell külön mezőket és metódusok deklarálni az osztályhoz, mert a scope kiterjed a Numbers változóra is. Vagy egy másik pl.:

    procedure TfoMain.btAnonymMethodsClick(Sender: TObject);
    var
      List: TList<TProc>;
      Proc: TProc;
    begin
      List:= TList<TProc>.Create;
    
      with List do
        try
          Add( procedure begin ShowMessage('A'); end );
          Add( procedure begin ShowMessage('B'); end );
          Add( procedure begin ShowMessage('C'); end );
    
          for Proc in List do
            Proc;
        finally
          Free;
        end;
    end;
    

    Ami nem csinál mást, mint egy generikus listába eltárolja az eljárás referenciákat és utána egyszerre meghívja mindet.

    Persze, azért ne legyen már minden tökéletes, vannak megkötések is, mint pl. globális eljárások és függvények nem lehetnek generikusak, illetve anonymous metódusok nem használhatók eseménykezelő tagfüggvényekként, mint pl.:

    Button1.OnClick:= procedure (Sender: TObject) begin end;

    E2009 Incompatible types: 'method pointer and regular procedure'

    Sajnos a reference esetében az of object nem működik. Máskülönben a fenti kód is működne. C# esetében ez persze teljesen más, mivel a C# tisztán objektumorientált nyelv. Sajnos ez nincs:

    type
      TProc<T> = reference to procedure(const Value: T) of object;
    

    Exit

    Van egy új Exit eljárás, ami lerövidíti a kódolást, mivel a pascalban nincs return, mint a C szerű nyelvekben:

    function GetValue(const Value: Integer): Integer;
    begin
      if Value < 0 then
      begin
        Result:= 0;
        Exit;
      end;
    
      //...
    end;

    Ehelyett most már elég csak ennyit írni:

    function GetValue(const Value: Integer): Integer;
    begin
      if Value < 0 then
        Exit(0);
    
      //...
    end;
    

    Az MSBuild rendszert is megreformálták egy kicsit. A compiler még mindig bitang gyors, de talán az egyik legfontosabb újítás a unicode támogatás. Mostantól az alapértelmezett string a Delphiben a unicode string és minden SaveToFile tagfüggvény támogatja a kódolási forma meghatározását:

    ListBox1.Items.SaveToFile('Readme.txt', TEncoding.UTF8);

    A VCL is továbbfejlődött, került bele jónéhány új control, mint például Office Ribbon controlok, csoportosítható ListView, s meglepően jó Vista támogatás (pl. a gomboknak van olyan tulajdonsága, hogy ElevationRequired). Emellett van egy kismillió új property, fel se lehetne sorolni.

    Történt jónéhány változás az adatkezelés terén is, mint pl. COM független adatelérés - illetve most ez ellen szólva - jobb és sokkal gyorsabb támogatás a COM könyvtárak használatához és készítéséhez. Amit most mondok, szintúgy meglepő, de a VCL for the Web támogatja a Silverlight 2.0-t. Az IDE tartalmaz néhány új eszközt, mint pl. Class Explorer, Resource manager, a lokalizáláshoz eszközöket, stb.

    "Egy szóval: Win32 platformra jobb fejlesztőeszközt el sem lehetne képzelni."

    Megint elhamarkodtam ezt a kijelentést. Miért?

    A generikus kollekciók némelyike totál bugos

    A TDictionary osztály AddOrSetValue metódusa dob egy Access Violation exceptiont, ha a default konstruktort hívva példányosítjuk az osztályt. Ha meg van adva a túltöltött konstruktor capacity paramétere 1-re legalább, akkor nincs ilyen gond:

    // ADictionary := TDictionary<string, string>.Create;
    ADictionary := TDictionary<string, string>.Create(1);
    ADictionary.AddOrSetValue(...);

    Plusz még memory leak is van, ha a Keys kolleckiót használom:

    ReportMemoryLeaksOnShutdown := True;
    ...
    for Key in ADictionary.Keys do
      WriteLn(Key);

    // ADictionary.Keys.Free;


    Ribbon Controlok

    Jók lennének, ha a DoubleBuffered működne rajtuk. Így viszont úgy villognak, hogy a frász tör ki tőlük. Egyáltalán nem esztétikus így.

    Generikus típusok

    Eszméletlen, hogy még mindig van bug a compilerben. Már a Delphi.NET esetében ezt megírtam egyszer, de egy bug még mindig maradt a compilerben a generikusakkal kapcsolatban.

    Illetve egy másik szépség, hogy a fordító nem kényszeríti ki az interfész implementálását generikus típusok esetén, csak abban az esetben, ha használom is az osztályt a kódban. Sima osztályok esetében jól működik, itt viszont nem kell implementálnom az interfész DoSomething metódusát, simán lefordul anélkül:

    type
      IMyInterface = interface
        procedure DoSomething;
      end;
    
    TMyClass<T> = class(TInterfacedObject, IMyInterface)
    end;

    Arról nem beszélve, hogy a kódszerkesztő még mindig allergiás a generikusok támogatására. Nem is kell megkönnyíteni a szerkesztést, nem fontos! Minek az nektek! :) Ez nem IntelliSense, hanem IntelliGenny.

    Generikus intefészek


    A következő konstrukciót a C# fordító képes kezelni, ellenben a Delphi compiler nem:

    [DCC Error] Unit1.pas(49): E2086 Type 'IEmployee' is not yet completely defined

    type
      IObservableObject<T> = interface
      end;
    
      IEmployee = interface(IObservableObject<IEmployee>)
      end;

    Szóval vannak itt akadályok, de alapvetően egész jó, már ahhoz képest, ami eddig volt.

    September 05

    Singleton (.NET)

    Első körben amiről szeretnék beszélni, mely egyaránt használatos Win32 és .NET fejlesztés során: a singletonok. Mindkét részről bemutatom ennek a használatát, de kezdjük először is azzal, hogy mi célt szolgál ez a pattern:

    Gondolom sokunkkal előfordult már, hogy egy osztály egyetlen példányára volt szükségünk. Ez az osztály definiál különböző műveleteket, melyet a kódon belül mindenhol el szeretnénk érni anélkül, hogy azt 76x példányosítanunk kellene. Nagyon jó példa erre a Delphi VCL Application objektuma, mely egy TApplication példány és pontosan egy létezik belőle a program futása során. Ugyanez igaz a .NET-re is. Speciel az Application objektumokkal egy a baj, hogy nem thread safek, azaz nem szálbiztosak. A következő singleton pattern megvalósítások viszont választ adnak mindkét kérdésre:

    Először is, ha már vannak generikusok a nyelvbe, akkor érdemes azt kihasználni és egy általános megoldást adni. Itt külön megemlíteném, hogy a Delphi 2009 for Win32 már támogatja a generikusokat és az anonym metódusokat is. Majd ezekről is beszélek később.

    C#

    public sealed class Singleton<T> where T: class, new()
    {        
        private static volatile T _instance;
                
        private static object _syncRoot = new object();
    
        private Singleton() { }
    
        public static T Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_syncRoot)
                    {
                        // lazy initialization
                        if (_instance == null)
                            _instance = new T();
                    }
                }
    
                return _instance;
            }
        }
    }

    Lehet ezt még cicomázni, de a lényeg ugyanaz. Egy-két kisebb technikai részlet: a volatile kulcsszó jelzi, hogy a mezőt módosíthatják konkurrens szálak, vagy bármi más, az mindig az aktuális értékét fogja tükrözni az objektumnak. A osztály megvan jelölve a sealed módosítóval, tehát nem lehet származtatni belőle. A konstruktor private, ezáltal még véletlenül sem példányosítható a Singleton<T> osztály. S végül: lazy initialization, azaz akkor példányosítjuk csak az osztályt, amikor először szükség van rá, nem szemeteljük tele indításkor a memóriát nem használt singleton objektumokkal. A használata pedig nagyon egyszerű:

    Singleton<List<string>>.Instance.Add("AAAAA");
    Singleton<List<string>>.Instance.Add("BBBBB");
    Singleton<List<string>>.Instance.Add("CCCCC");
    
    foreach (var s in Singleton<List<string>>.Instance)
        Console.WriteLine(s);
    
    Console.ReadLine();
    Az első Singleton<List<string>>.Instance automatikusan példányosít egy List<string> generikus listát és a többi már szintén ebbe fog pakolgatni. Tehát ezzel a Singleton segédosztállyal típusonként készíthetünk pontosan egy példányt, amelyhez különböző szálakból is hozzáférhetünk.

    Delphi.NET 2.0

    unit VVMF.Delphi.Common.Singleton;
    
    interface
    
    type
      TSingleton<T: class, constructor> = class sealed
      strict private
        class var FInstance: &Object;
        class var FLock: &Object;
    
        class function SafeGetInstance: T; static;
        class procedure SafeSetInstance(Value: T); static;
        class function GetInstance: T; static;
    
        class constructor Create;
        constructor Create;
      public
        class property Instance: T read GetInstance;
      end;
    
    implementation
    
    uses
      System.Threading;
    
    //------------------------------------------------------------------------------
    // class contructor
    //
    class constructor TSingleton<T>.Create;
    begin
      FLock := &Object.Create;
    end;
    
    //------------------------------------------------------------------------------
    // default constructor
    //
    constructor TSingleton<T>.Create;
    begin
      inherited;
    end;
    
    //------------------------------------------------------------------------------
    // instance
    //
    class function TSingleton<T>.GetInstance: T;
    begin
      if SafeGetInstance = nil then
      begin
        Monitor.Enter(FLock);
        try
          if SafeGetInstance = nil then
            SafeSetInstance(T.Create);
        finally
          Monitor.Exit(FLock);
        end;
      end;
    
      Result := SafeGetInstance;
    end;
    
    //------------------------------------------------------------------------------
    // C# volatile = VolatileRead
    //
    class function TSingleton<T>.SafeGetInstance: T;
    begin
      Result := T(Thread.VolatileRead(FInstance));
    end;
    
    //------------------------------------------------------------------------------
    // C# volatile = VolatileWrite
    //
    class procedure TSingleton<T>.SafeSetInstance(Value: T);
    begin
      Thread.VolatileWrite(FInstance, &Object(Value));
    end;
    
    end.

    Ez megegyezik a C# implementációval. A különbség először is, hogy nincs volatile kulcsszó. Ez persze csak nyelvi fícsör a C#-ban, nem más mint a Thread.VolatileRead és Thread.VolatileWrite metódusának lerövidítése. Nincs lock se. Ez szintén orvosolható a Monitor.Enter / Monitor.Exit -el. A metódusok amellett, hogy osztálymetódusok (class procedure, class function) megvannak jelölve static kulcsszóval is, amely annyit tesz, hogy a Self (C# this) nem elérhető. Az osztály Delphi.NET-ben szintén megjelölhető sealed-ként. Az FInstance Object, mert a Volatile Read/Write ezt kívánja meg. Szóval azért látszik, hogy ez annyira nem szép a C# kódhoz képest, de használni használható, az eredmény kb. ugyanaz:

    TSingleton<List<string>>.Instance.Add('AAAAA');
    TSingleton<List<string>>.Instance.Add('BBBBB');
    TSingleton<List<string>>.Instance.Add('CCCCC');
    
    for Value in TSingleton<List<string>>.Instance do
      WriteLn(Value);
    
    ReadLn;

    SQL Server Management Studio 2008 - NEWSEQUENTIALID

    Igen, szóval vannak akadályozó tényezők. Ezt csak érdekességként írom azoknak, akik még szintén nem használták ezt, mert elsőre elég furának tűnt a dolog, hogy nem akart minden zökkenőmentesen történni. Ha newsequentialid()-et használunk és elkezdjük módosítani a táblát, hamar szembesülhetünk a következő validációs üzenettel:

    Validation Warnings

    Ez nem tudom, hogy bug vagy direkt így működik, de amit később kaptam az arcomba az már érdekesebb volt, pontosan amikor módosítani akartam a táblaszerkezetet és aztán menteni:

    Saving changes is not permitted.

    Szerencsére van megoldás, ki kell kapcsolni a prevent saving changes that require table re-creation-t:

    Prevent saving changes that require table re-creation