János's profileJános JankaPhotosBlogListsMore ![]() | Help |
|
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:
Í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):
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: DestruktorokA .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.
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ő:
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ípusokA 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ípusokA 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:
InterfészekCsak, 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: 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: 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; 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? 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; 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: var N: Nullable<Integer>; begin N := nil; end. [DCC Error] Generics.dpr(12): E2010 Incompatible types: 'Nullable<System.Int32>' and 'Pointer' 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? 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 CompilerA 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:
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á! June 03 C# 3.0 és Delphi.NET - Extension methodsNé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: 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. |
|
|