János's profileJános JankaPhotosBlogListsMore Tools Help

Blog


    June 20

    Delphi vs C#

    Úgy látom, most érkezett el az idő, hogy megpróbáljam akkor tisztázni, hogy mi a megfelelő szintaxis C#-ban azoknak a nyelvi elemeknek, amiket eddig Object Pascalban megszokhattunk, ill. fordítva. Ez egy jó hosszú cikk lesz, de talán segít azon veterán Delphi fejlesztőkön, akik idáig még nem láttak C#-ot. Külföldön már születtek elég átfogó cikkek, de úgy döntöttem ide pakolom a hazai Delphi fejlesztőknek az érdekesebb és nap mint nap előforduló eshetőségeket. A legyegyszerűbb persze a szokványos táblázatos forma, kezdve első körben azon Delphi sajátosságokkal, amik C#-ban kicsit másképp vannak:

     

    Description Delphi C#
    Platform-leveling compiler magic class helpers

    Explicit static methods C# 3.0 extension methods

    a.k.a. reverse P/Invoke Unmanaged exports Use C++, hacks
    a.k.a. dynamic P/Invoke Virtual Library Interfaces Use unmanaged C++, hacks
    Limited to ordinal types with <= 256 elements sets enum flags, BitArray,
    int bit-fiddling
    Meta classes class of references System.Type
    Meta class polymorphism virtual class methods System.Type, reflection
    Poor-man’s generics Type-less var and out parameters C# 2.0 Generics, System.Object function
    Logical vs. actual types type aliases, typed types Explicit typing
    Simpler than overloading Default parameters Overloading
    Simplified internationalization resourcestrings Resources and ResourceManager.GetString
    Simulated using overloading Named constructors Overloading
    Dispatching windows messages message methods WndProc switch
    One-type fits all Variants System.Object boxing
    Non-OOP code Global routines Static methods of a class
    Non-OOP data Global variables Static fields of a class
    Multiple array properties Named array properties

    Overloaded this indexer
    Nested class with this indexer

    Implementation hiding, automatic access to outer variables Local (nested) procedures private method, anonymous method (C# 2.0)
    Structure overlaying (union) variant records (case)

    [StructLayout(LayoutKind.Explicit)],
    [FieldOffset()]

    Easy input/output Text files, Writeln, etc Console and Stream classes
    Cross-platform capabilities Supports Win32, Linux

    Use C/C++,
    Mono for Linux

    Íme néhány C# 1.0-s újdonság, amiknek egyáltalán nincs Object Pascalos megfelelőjük (értsd ezt, hogy nyelvi szinten):

     

    Description C# Delphi
    Thread synchronization lock
    Monitor.Enter(O); 
    try
    ...

    finally
    Monitor.Exit(O); end;
    Garbage collection object pinning fixed
    H := GCHandle.Alloc(...) 
    try
      P := H.AddrOfPinnedObject; 
    finally 
      H.&Free;
    end;
    Deterministic releasing of unmanaged resources using
    O := TMy.Create; 
    try
      ...
    finally 
      O.Free; 
    end;
    Garbage collection deallocate notification C# destructor ~ClassName override Finalize method
    Unsafe code temporary allocations stackalloc GCHandle.Alloc dynamic array
    Integer arithmetic overflow checking

    checked/
    unchecked

    {$OVERFLOWCHECKS ON/OFF}
    {$Q+/-}
    Read-only fields initialized in a constructor readonly field const, read-only property, normal field
    Set function result and return to caller return
    Result := O; 
    Exit;
    May be modified outside current thread volatile field Explicit locks, Thread.VolatileRead/Write
    Per-assembly cross-class implementation details ternary ? : operator if .. then .. else, IfThen routines
    Multi-case testing of strings switch (string) nested if..then..else, TStringList.IndexOf

    Most feltételezve azt, hogy a kedves olvasó még nem találkozott semmilyen formában a .NET-el, próbálok mindenről egy rövid leírást is adni, elvégre szerintem több Win32 Delphi fejlesztő van, mint Delphi.NET fejlesztő. Az most lehetetlen, hogy én ide leírjam az egész SDK-t magyarázatnak, csak egy-két mondattal a lényegét az adott témának.

    Első körben következzen néhány érdekes különbség a két nyelv között:

    Destruktorok

    A .NET saját szemétgyűjtővel rendelkezik (GC), ami felügyeli az objektumok életét. Mihelyst nincs szükség valamire, azt kipucolja automatikusan a memóriából abban a pillanatban, ahogy ideje lesz rá. Ergo nincs szükség az eddigi általános értelemben vett memóriakezelési minták használatára (Free, Destroy). Viszont mégis, néha szükségessé válhat a nem managelt erőforrások kipucolása a memóriából. Erre használható az IDisposable interfész.

    Delphi.NET-ben a szokásos Destroy destructor felülírása megfelel az IDisposable interfész implementálásának (visszafele kompatibilitási okokból). Ugyanezt C#-ban explicit módon kell megtenni. Másik oldalról nézve pedig, C#-ban a destructor felülírása megfelel a Finalize metódus felülírásának (nem is lehet a Finalize-t csak úgy felülírni, ill. hívogatni C#-ban), míg ezt Delphiben a szokványos tagfüggvény felülírási módszerrel (override) lehet megtenni.

     

    class MyClass : IDisposable
    {
        public void Dispose() {
            // ide jön a takarító kód
        }
        
        ~MyClass() {
            // Finalize() felülírása (GC hívja)
        }
    }
    type
      TMyClass = class
      strict protected
        procedure Finalize; override;
      public
        destructor Destroy; override;
      end;
    
    implementation
    
    destructor TMyClass.Destroy;
    begin
      // IDisposable.Dispose
      // megvalósítása
      inherited;
    end;
    
    procedure TMyClass.Finalize;
    begin
      inherited;
      // Finalize felülírása
    end;
    

    C#-ban minden osztály, amely megvalósítja az IDisposable interfészt, használható egy using blokkon belül, ami automatikusan biztosítja a Dispose meghívását. Természetesen ugyanez explicit módon is megtehető:

     

    using (var myObject = new MyClass())
    {   
        // ide jön a kód
    }
    // explicit módon...
    var myObject = new MyClass();
    try {
        // ide jön a kód
    }
    finally {
        myObject.Dispose();
    }
    var
      myObject: MyClass;
    begin
      myObject := MyClass.Create;
      try
        // ide jön a kód
      finally
        myObject.Free;
      end; 
    

    Megjegyzendő: Delphi.NET-ben szintén van Free metódus, mint Delphi Win32-ben, mely megnézi, hogy az osztály implementálja-e az IDisposable interfészt, s ha igen, akkor meghívja annak Dispose metódusát. Persze jó kérdés, honnan tudjuk Delphi.NET-ben, hogy szükséges-e ezt a Free-t állandóan hívogatni, avagy sem? Csak onnan, hogy meg kell nézni, van-e Dispose metódusa az osztálynak. A Free létezése mindössze visszafele kompatibilitást szolgál, viszont Delphi.NET-ben a VCL.NET-es gyökerek miatt, ami továbbra is erősen épít a Win32-es gyökerekre, nem elengedhető a Free hívogatása VCL-es objektumok esetén.

    Generikus típusok

    A generikus típusok jelenleg támogatottak a Delphi .NET 2.0-ban. A típuskövetkeztetés jelenleg nem működik megfelelően, hiányos. Illetve mégegy apróság, hogy a megszorítások "idiótán" vannak implementálva. Egy pl.:

    Tegyük fel, hogy akarunk egy generikus metódust készíteni .NET-ben, amellyel végigiterálunk a TA típusú lista TB típusú elemein. Minden osztály, amely megvalósítja az IEnumerable interfészt iterálható (C# foreach, ill. Delphi for...in). A probléma csak az, hogy Delphi.NET-ben a megszorításokat a típusparaméterek mellett kell megadni, ezért a fordító a balról jobbra történő kiértékelés miatt nem fogja tudni, hogy mi az a TB:

    procedure THelper.ForEach<TA: IEnumerable<TB>, TB> (...)

    Ugye praktikusan úgy írnánk le, ahogy fentebb is van, csak a gond az, hogy a TB a második típusparaméter, ezért a fordítónak fogalma sincs még róla. A megoldás, hogy megfordítjuk a típusparamétereket:

    procedure THelper.ForEach<TB, TA: IEnumerable<TB>> (...)

    Nem túl elegáns így, de működik. Ellenben C#-ban nem véletlenül létezik where:

    public static void ForEach<TA, TB>(...) where TA: IEnumerable<TB>

    Nullázható típusok

    A nullázható típusok lényege, hogy lehetőség van őket, mint ahogy a nevük is mutatja nullázni (C# - null, Delphi - nil). Ez tulajdonképpen egy generikus osztály a .NET-ben: Nullable<T>, ahol T jelöli a típust, mely értéktípus is lehet természetesen. Ehhez a C# annyival tesz többet, hogy működnek rá a szokványos operátorok, ill. van egy ?? operátor is a kezelésükre.

    Egy pl erre is:

     

    int? a = null;
    int i = a ?? 0;
    var
      A: &Nullable<Integer>;
      I: Integer;
    begin
      A := Default(&Nullable<Integer>);
    
      if A.HasValue then
        I := A.Value
      else
        I := 0;

    Interfészek

    Csak, hogy akadjon olyan is, ami nincs a C#-ban: pl. nem lehet átruházni az interfész implementációt egy olyan osztályra, ami már egyszer megtette azt. Ez utánanézve, emlékeim szerint egy Delphi 5-ös feature volt (1995), lassan nem árt, hogy a C#-ba is beletegyék:

    type
      IMyStuff      = interface end;
      IMyOtherStuff = interface end;
    
      TMyStuff      = class(TInterfacedObject, IMyStuff);
      TMyOtherStuff = class(TInterfacedObject, IMyOtherStuff);
    
      TMyClass = class(TInterfacedObject, IMyStuff, IMyOtherStuff)
      private
        FMyStuff     : IMyStuff;
        FMyOtherStuff: IMyOtherStuff;
      public
        constructor Create;
    
        property MyStuff     : IMyStuff      read FMyStuff      implements IMyStuff;
        property MyOtherStuff: IMyOtherStuff read FMyOtherStuff implements IMyOtherStuff;
      end;
    implementation
    
    constructor TMyClass.Create;
    begin
      FMyStuff      := TMyStuff.Create;
      FMyOtherStuff := TMyOtherStuff.Create;
    end;
    

    A végére pedig a C# 3.0 nyelvi specifikáció, mely minden porcikájában új a Delphi fejlesztőknek és a Pascal nyelv ismerőinek (értsd ezt, hogy jelenleg hasonló sincs a OP nyelvben ezekhez, kivéve az Extension methodokat, melyek kb. azonosak a Delphi class helpereivel): C# Language Specification

    De nem kell messzire menni, hogy mutassak egy másik példát is:
    Oxygene 3.0, ami nem más mint az Object Pascal következő generiációs, C# 3.0-s megfelelője, sőt jelenleg annál is több, ha még ezt is beleszámoljuk: Parallel loops

    Nyugodtan lehet kérdésekkel hozzám fordulni (általános iskola 4-5. osztálya óta a Pascal nyelvvel foglalkozom, ezen nőttem fel, folytatva később a Delphivel), úgyhogy amiben tudok segítek. Pl. volt a napokban egy jó kérdés: A Delphi TAction, TActionList osztályait mi váltja ki a WPF-ben? A lehető legjobb alternatívát tudom mondani: a WPF commandok. Ezekről is írtam már korábban: Commandok a WPF-ben. De nyugodtan lehet bármilyen hasonló, a témával kapcsolatos kérdést itt a blogomon feltenni, szívesen válaszolok, szólva hajrá!

    Addig is jó tanulást mindenkinek!

    June 06

    Generikus típusok támogatása (Delphi.NET 2.0)

    Jaaaj! Mégegy fontos dolgot elfelejtettem megírni, amikor teszteltem, de az maga a csúcs volt:  Mi a francnak vannak megszorítások, ha nem működik az alapján normálisan a type inferencing. Mindenre működik (class = referencia típusok, interface = interfészek, record = érték típusok), kivéve ha egy konkrét osztályt adok meg (köszönet a tesztért chikk C#-os FormFactory osztályának):

    type
      IFormFactory = interface
        function get_Name: string;
        property Name: string read get_Name;
    
        function CreateForm: TForm; overload;
        function CreateForm(const Arguments: array of const): TForm; overload;
        function CreateForm(Initializer: Delegate): TForm; overload;
      end;
    
      TFormFactory<T: TForm> = class(TInterfacedObject, IFormFactory)
      private
        FOnlyOne: Boolean;
        FCurrent: T;
        procedure Hook;
        procedure FormDestroy(Sender: TObject);
      public
        constructor Create; overload;
        constructor Create(OnlyOne: Boolean); overload;
    
        function CreateFormT: T; overload;
        function CreateFormT(const Arguments: array of const): T; overload;
        function CreateFormT(Initializer: Action<T>): T; overload;
    
        function CreateForm: TForm; overload;
        function CreateForm(const Arguments: array of const): TForm; overload;
        function CreateForm(Initializer: Delegate): TForm; overload;
    
        function get_Name: string;
        property Name: string read get_Name;
      end;

    Első eltérés, hogy a generikus metódusoknak más nevet kell adni (CreateFormT), mert az Object Pascalban max. az átnevezés használható alternatíva, amivel csak az a gond, hogy a compiler a túltöltött interfész metódusokat nem képes egymástól megkülönböztetni, így a már kommentek között említett módszer nem működőképes megoldás egyszerre 3 ugyanolyan nevű metódushoz. Úgyszint nem lehet a függvények neve Create a konstruktor miatt.

    De ez idáig ok, senkit nem érdekel, viszont most jön az igazán tuti dolog:

    function TFormFactory<T>.CreateFormT: T;
    begin
      if FOnlyOne then
      begin
        if FCurrent = nil then
        begin
          FCurrent := T.Create(Application);
          Hook;
        end;
    
        Result := FCurrent;
      end
      else
        Result := T.Create(Application);
    end;
    

    Azt mondja a fordító az FCurrent = nil -re:

    [DCC Error] FormFactory.pas(82): E2015 Operator not applicable to this operand type

    Persze ha a típusparamétert class-ra állítom (TFormFactory<T: class>) akkor működik, kivéve ha konkrét osztályt adok meg a megszorításokban. S ennek köszönhetően persze csak így működik a dolog:

    function TFormFactory<T>.CreateFormT(const Arguments: array of const): T;
    begin
      if FOnlyOne then
      begin
        if TForm(FCurrent) = nil then
        begin
          FCurrent := T( Activator.CreateInstance(TypeOf(T), Arguments) );
          Hook;
        end;
    
        Result := FCurrent;
      end
      else
        Result := T( Activator.CreateInstance(TypeOf(T), Arguments) );
    end;
    

    Ennek is így mi értelme?

    type
      TFormFactories = class sealed
      private
        FFactories: Dictionary<string, IFormFactory>;
      public
        constructor Create; overload;
        constructor Create(const Factories: array of IFormFactory); overload;
    
        procedure Add(Factory: IFormFactory);
        function Get<T: TForm>(const Name: string): TFormFactory<T>;
    
        function get_FormFactories(const Name: string): IFormFactory;
    property FormFactories[const Name: string]: IFormFactory read get_FormFactories; default; end;
    function TFormFactories.Get<T>(const Name: string): TFormFactory<T>; begin Result := TFormFactory<T>(FFactories[Name]); end;

    Hiába van odaírva neki, hogy Get<T: TForm>, ezt vágja a képembe:

    [DCC Error] FormFactory.pas(198): E2515 Type parameter 'T' is not compatible with type 'TForm'

    Ez azért így szintén elég nagy gáz. Én nem értem komolyan mondom, ki tesztelte ezt ott?

    Workaround:

    Most már biztos, hogy compiler bug, nézzük csak ezt az egyszerű példát:

    unit Generics.Samples;
    
    interface
    
    type
      TForm = class
      end;
    
      TFactory<T: TForm> = class
      end;
    
      TFactories = class sealed
        function Get<T: TForm>: TFactory<T>;
      end;
    
    implementation
    
    function TFactories.Get<T>: TFactory<T>;
    begin
    end;
    
    end.

    A Get metódus egy TFactory<T> típusú objektumot adna vissza, ahol T egy TForm származék. A fordító a Get metódus deklarációs részénél figyeli is a típusparaméter megszorítást, ha onnan lehagyom a : TForm -ot, akkor már ott rinyál emiatt:

    [DCC Error] Generics.Samples.pas(13): E2515 Type parameter 'T' is not compatible with type 'TForm'

    Tehát az osztálydeklaráció ok, jól is működik, viszont az implementációs résznél teljesen megkavarodik, nem képes észrevenni, hogy a T egy TForm a deklaráció alapján. Félreértések végett ezt a vázat a Delphi automatikusan generálta a CTRL+C megnyomásával és ennek így jónak is kellene lennie, s jó is, ha nem egy generikus típussal tér vissza a függvény. Következő lehetőség, ha egy kicsit meggyepálom az implementációját a függvénynek, hiszen ott úgyse kell feltüntetni újra a paramétereket. A kettő közül bármelyik jó:

    function TFactories.Get;
    begin
    end;
    function TFactories.Get<T>;
    begin
    end;

    S láss csodát, működik minden:

    function TFactories.Get<T>;
    begin
      Result := TFactory<T>.Create;
    end;

    Ezek alapján a fenti metódus a következőképp valósítható meg:

    function TFormFactories.Get<T>;
    begin
      Result := TFormFactory<T>(FFactories[Name]);
    end;

    Annyi, hogy itt most nem látjuk rögtön a paramétereket és azt, hogy milyen típusú objektummal kell visszatérni. Az a szerencséjük, hogy a compiler már rég óta megengedi ezt a fajta lazaságot.

    Mégegy apróság, nullázható típusok:
    A nullázható típusok nem nullázhatók (legalábbis a hagyományos módon nem):

    var
      N: Nullable<Integer>;
    begin
      N := nil;
    end.
    

    [DCC Error] Generics.dpr(12): E2010 Incompatible types: 'Nullable<System.Int32>' and 'Pointer'

    Persze ez annak a következménye, hogy a nullázható típusok nem támogatottak a nyelvben, pedig úgy volt anno megírva. Így nem alkalmazhatóak rá a szokványos nyelvi operátorok sem. Mindegy, nem fontos, ne foglalkozzatok vele! Végül is minek az, kinek kellenek? :) Erre viszont tudok ajánlani mást, mivel a nullázható típusok generikus típusok és a default úgyszint működik a delphiben, szóval ezt nyugodtan lehetne használni helyette:

    var
      N: Nullable<Integer>;
    begin
      N := Default(Nullable<Integer>);
    end;

    Na mindenesetre én ezt éles projektre soha nem használnám. Olyan bugos és ráadásul nem is kicsi dolgok ezek, hogy nem éri meg a fáradságot. Az egész csak lassabb kódot és több gépelést igényel, meg egy millió workaround írását. S még az IDE is csak hátráltat, nem beszélve a szétfagyó IntellSense-ről. Nem tudom mi a véleményetek, de ha a CodeGear nem embereli meg magát, akkor elbúcsúzhat a Delphitől végleg. 2 év alatt beleműtöttek egy félig kész generikus típustámogatást, de ennyi, mást már nem is tettek a nyelvbe, de már anonym metódusokat és generikusokat ígérnek, nem is tudom, tán Win32-höz?

    Delphi Language Enhancements

    Félreértés végett én szeretem magát a nyelvet, de amit a cég csinál mostanában, vagyis ilyen bugos, 2-3 hónap alatt összecsapott munkát kiadni és árulni kemény százezrekért, LOL. Ja igen és sajnos a nyelv maga is igen bugos, ami viszont már tényleg arcátlan, vagy egyszerűen nem tudom elképzelni, milyen tesztelő figurák élnek ott? Vááá. Én kapásból 1-2 óra leforgása alatt találtam olyan kemény bugokat, amiket egy tesztelőnek egy ilyen neves cégnél mindenképp illene kiszúrnia. De már nem is folytatom tovább a kutatást, mert lehet találnék még vagy 500 bugot, csak felidegesítem magam vele. :) De így már megértettem végre, hogy mitől olyan bugos a VCL néhány ponton, ráadásul már lassan 5 éve minden kiadásban, mindig reménykedtem, hogy a következőben ezt a kis trivi problémát kijavítják, sőt reportot is kaptak már róla másoktól, hogy pl. az Indy komponensek memóriaszivárgást okoznak, vagy a Frame-k frissítési gondjai is ilyenek, stb.

    June 05

    Delphi .NET Bug Compiler

    A rendezvény alkalmából, s mert látom egyre többen kaptak kedvet a .NET-hez : Win32, Delphi, .NET 3.5 fejlesztői nap - sőt, még szeretnének többen is hasonló rendezvényeket - most pontot teszek az "i"-re végleg .NET téren:

    Azt hiszem, amit most írok tanulságos lesz minden fejlesztő számára, aki .NET fejlesztésre adja a fejét Delphiben. Leteszteltem minden feature-t és alternatívát a Delphi.NET oldalon a VS-el szemben, íme a tapasztalataim:

     

    • A generikus típusok támogatása részleges, nincs normális "code completion" és újrafaktorizálási támogatás hozzá.
    • Az "IntelliSense" képes néha elrántani az egész RAD Studio-t. Ilyenkor bezakkantja a projekt fájlunkat is természetesen. Arról nem beszélve, hogy sokkal lassabb is, mint a VS IntelliSense és sokkal "butább" is.
    • A build konfigurációt nem tudom miért, elfelejti néha. Megkeveredik teljesen. Ez az MSBuild rendszer óta van így, gondolom nem sikerült 100%-an tökéletesen implementálni még :).
    • Valamikor a fordító nem lát egy névteret. Hiába adom hozzá újra az assemblyt, csak nem látja. Ilyenkor újra létre kell hozni az egész projektet és hozzáadni egyesével megint mindent, semmi más nem segít rajta.
    • A class helperek szintén nem működnek kívülről, ilyenkor a fordító belső hibával megzakkan.
    • Nincsen semmilyen támogatás a nullázható típusokhoz.
    • Nincsenek anonymous metódusok =>
    • Nincsenek lambda kifejezések =>
    • Nincs LINQ (LINQ to Objects, LINQ to DataSet, LINQ to SQL, LINQ to XML)
    • Az Entity Framework referenciákat hozzáadva a projekthez (mint pl. System.Data.Entity) a fordító úgyszint belső hibával elszáll (a .NET 3.5 már nem támogatott :), de igaz egy-két .NET 3.0-s assemblyre is): Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED)) Ilyenkor a compiler úgy megzakkan, hogy csak a RAD Studio újraindítása segít rajta, innentől kezdve már semelyik projectet nem hajlandó lefordítani.
    • A Debug osztályt nem támogatja a compiler. Működik szépen Debug és Release fordításban is. Ez azért elég gáz így.
    • Az ECO-val is vannak gondok, nem tudom, hogy csinálta, de kitörölt 3 táblát az adatbázisomból. Ez azért már sokkal gázosabb dolog mindennél. De tegyük fel, hogy én néztem el valamit, bár nem hiszem, akkor miért nem kérdezett rá?
    • Nincsen semmilyen adatkötési támogatás, ez persze a VCL.NET miatt lehetetlen is, a WinForms támogatást meg kiszedték. Egyedül az ASP.NET működik, de az a designer is olyan, hogy bárcsak ne lenne. WPF természetesen nincs, se az anno beígért VCL for WPF :).
    • A DCCIL compiler nem képes rengeteg olyan dolgot ellenőrizni, amit másik oldalon a C# compiler fordítási időben ellenőriz. Így szépen futásidőben száll el a program valamilyen exceptionnel. A legtutibb viszont, amikor olyan egyszerű dolgoktól is képes megzakkani és belső hibákat generálni, mint egy nyamvadt generikus típus.
    • Vannak olyan kis finomságok, mint pl. a yield return a C#-ban, aminek szintén nincs Delphi (Object Pascal) megfelelője.
    • Az IDE nagyon gyenge és eszköz szegény .NET-hez a VS IDE-hez képest.
    • Nincs workflow támogatás a Delphiben. A WCF támogatás is olyan, hogy egy-két assemblyt szabályosan bele kell hackelni, hogy működjön, ennyit a .NET 3.0 kompatibilitásról.

    Ez kívül még van sok millió kis bug a .NET compilerben és az IDE-ben egyaránt. Pl. az XML Data Binding is egy vicc, olyan mappinget generál, hogy ha megváltozik egy attribútum is az XML-ben, vagy akár egy új kerül be, akkor nem ellenőrzi kiolvasáskor, hogy létezik-e és nincs is rá semmilyen kallantyú, hogy olyan kódot generáljon le, ami jó lenne, tehát lehet átírni kézzel ezer helyen mindent.

    Az ECO szintén egy vicc, vagy fél órát el kellett szöcskézzek vele, míg rájöttem, hogy lehet legyártatni vele a mappinget egy már meglévő SQL Server adatbázishoz, úgy, hogy utána ki is törölte valahogy a tábláimat, LOL. Azért ilyet a Microsoft .NET Entity Framwork Beta változatai sem csinálnak.

    Összességében a vélemény az, amit a cím is mond, a Delphi.NET compilere egy nagy bug halmaz és ezt csak 1-2 órányi tesztelés alapján mondom, mi lenne, ha már legalább 1 hónapja használnám? Jaaaj. Érdekes, hogy ez viszont annyira Delphi Win32 oldalon nem jött át, pedig ott is feltudnék sorolni jópár bugos dolgot, mint pl. Framek, vagy egy nagy kódbázis esetén teljesen megkavarodik a compiler, akárcsak a kódszerkesztő, stb., stb. (Ez utóbbi viszont a DCCIL-re is igaz, ahogy láttam másokat külföldön erről nyilatkozni).

    S most itt a reklám helye: itt az ideje egy sokkal minőségibb terméket használni, bár tény, a VS se tökéletes, nem is szándékozom beharangozni, mint a megtestesült tökélyt, csak az nem mindegy, hogy milyen hibák és mekkora mennyiségben fordulnak elő benne. Ezenfelül pedig drasztikusan jobb, kényelmesebb és nagyobb eszközkészlettel is rendezlkezik.

    Szóval, hajrá!
    Örülök, hogy a prog.hu alapján is egyre több ember kezdi észrevenni, hol is tart a Világ!

    June 03

    C# 3.0 és Delphi.NET - Extension methods

    Néhány dolgot akkor a konferenciáról ami elhangzott így összehasonlításképp kiemelnék. Első körben - nem tudom most mért pont ezt választottam,  így jött - miben különböznek a C# bővítő metódusai a Delphi class helpereitől? Nézzünk egy példát annak mintájára, ami az előadáson is volt (most a Delphi .NET 2.0-hoz viszonyítok):

    unit Extensions;
    
    interface
    
    type
      TStringHelper = class helper for &String
      public
        procedure ForEach(Proc: Action<Char>);
      end;
    
    implementation
    
    procedure TStringHelper.ForEach(Proc: Action<Char>);
    var
      C: Char;
    begin
      for C in Self do
        Proc(C);
    end;
    
    end.

    Ez a kis dolog nem csinál semmi mást, mint kibővíti a string osztályt egy ForEach metódussal anélkül, hogy mi belekontárkodtunk volna a tényleges osztály kódjába, amire jelen esetben lehetőségünk sem lenne. A &String = System.String, csak lehetőség van így rövidíteni, Self = this (ami jelen esetben egy string), a for ... in pedig ugyanaz, mint C#-ban a foreach. LINQ nélkül viszont elég cicókás ezt használni:

    program Test;
    
    {$APPTYPE CONSOLE}
    
    uses
      SysUtils,
      Extensions in 'Extensions.pas';
    
    procedure Proc(Value: Char);
    begin
      Console.WriteLine(Value);
    end;
    
    var
      S: string;
    begin
      try
        S := 'Kiss Pista';
        S.ForEach( Proc );
    
        Console.ReadLine;
      except
        on E:Exception do
          Writeln(E.Classname, ': ', E.Message);
      end;
    end.

    Először is mindenhol, ahol szeretnénk használni ezt a metódust, fel kell venni a uses-ba az Extensions-t. Ez igaz minden unitra, mivel a Delphiben ezek külön névterek alapból. Másodszor se anonym metódusok nincsenek, se lambda kifejezések, így hát marad a régi ugrálósdi és gépelősdi módszer, mint fentebb is látszik. Harmadszor pedig, ha általánosan szeretnénk megfogalmazni valamit, s hát miért ne tennénk, hisz vannak generikus típusok, akkor nem tudjuk korlátozni, hogy a Delphi csak ott engedje használni ezt a bővítőmetódust, ahol az IEnumerable interfész is támogatott, mivel a következő konstrukció nem működik:

    type
      THelper<T> = class helper for T
      public
        procedure ForEach<TE>(Proc: Action<TE>);
      end;

    Erre beint a fordító és azt mondja:
    [DCC Error] Extensions.pas(16): E2508 type parameters not allowed on this type

    Félreértések elkerülése végett, ez nem azt jelenti, hogy általánosan nem lehetne megfogalmazni ezt, csak éppen lassabban, mivel saját ellenőrzésekre van szükség, ahelyett, hogy megszorításokkal elrendeztük volna a dolgot:

    unit Extensions;
    
    interface
    
    uses
      System.Collections,
      System.Collections.Generic;
    
    type
      THelper = class helper for &Object
      public
        procedure ForEach<T>(Proc: Action<T>);
      end;
    
    implementation
    
    procedure THelper.ForEach<T>(Proc: Action<T>);
    var
      Value: T;
    begin
      if Self is IEnumerable<T> then
      begin
        for Value in IEnumerable<T>(Self) do
          Proc(Value);
      end
      else
        raise EInvalidCast.Create(
          Self.GetType.FullName +
          ' as IEnumerable<' + TypeOf(T).FullName + '>'
        );
    end;
    
    end.

    Ezért gyorsabb a C# kód:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace CSharpTest
    {
        public static class Extensions
        {
            public static void ForEach(this string ret, Action<char> action)            
            {
                foreach (var element in ret)
                    action(element);
            }
    
            public static void ForEach<TA,TB>(this TA ret, Action<TB> action)
                where TA: IEnumerable<TB>
            {
                foreach (var element in ret)
                    action(element);
            }
        }
    }

    Ránézésre már látszik a különbség. Először is itt az első this-es paraméter mondja meg, hogy melyik osztályt szeretnénk bővíteni, aminek köszönhetően akár ez is lehet generikus típus. Erre aztán alkalmazhatjuk azt a megszorítást, hogy implementálnia kell az IEnumerable<elemek> interfészt, hogy végig tudjunk iterálni rajta a foreach-el. Használhatjuk a var kulcsszót anélkül, hogy nekünk minden változót egyesével deklarálnunk kellene, ezt az automatikus típuskövetkeztetés megoldja. S mégegy örömteli hír, elég csak ennyit írni:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Diagnostics;
    
    namespace CSharpTest
    {
        class Program
        {
            static void Main(string[] args)
            {  
                string d = "Kiss Pista";
                
                // 1. lambda expression
                d.ForEach(x => Console.WriteLine(x));
                // 2. anonymous method
                d.ForEach(delegate(char c) { Console.WriteLine(c); });
                // 3. simple method
                d.ForEach(Method);
                            
                Console.ReadLine();
            }
    
            static void Method(char value)
            {
                Console.WriteLine(value);
            }
        }
    }

    Az most teljesen mindegy, hogy ezek mögött compiler trükkök vannak, ugyanis a Delphibe még most sem támogatottak a névtelen metódusok sem, így ott még a 2.-es megoldás sem használható. Ja igen, s mégegy hátránya a Delphis megoldásnak, hogy ezzel a módszerel nem működik az overloading olyan szinten, hogy az ObjectHelper.ForEach generikus tagfüggvényét eltakarja a StringHelper.ForEach metódusa.

    Szerintem erről nem is kell többet beszélni, de ha valami még eszetekbe jut esetleg ezzel a témával kapcsolatban, akkor nyugodtan írjátok meg.