Delphi Prism – Mit tud, amit a Delphi nem?

Ahhoz képest, hogy ennyire rossz, elég olvasott. Csak nem lapulnak Delphi fejlesztők is köztünk? 😀 Most egy natív/menedzselt összehasonlítás következik, még mielőtt valami hittérítőnek titulálnának itt engem a C# “fanszőrök”. Egyébként meg, aki a Delphi 1.0 compiler-t tervezte, az tervezte a C#-ot is, ha már okulni kell valamiből, hogy honnan lett egy kismillió fícsör csincselve (Íme Anders Heijlsberg bátyánk története, aki még nem ismerné esetleg).

Delphi

Delphi Prism
Korábbi Delphi változatokban az alapértelmezett string az AnsiString volt. Jelenleg már a natív Delphi 16 bites unicode string-et használ alapértelmezésben. Ellenben a .NET-es stringel, a natív Delphi string változtatható, index alapján a karakterek lekérdezhetők és módosíthatók, viszont nem zero alapúak. Az első karakter indexe: 1. Két fajta string is van (ShortString (max. 255 karakter), LongString), de a 32 bites Delphi óta a string = LongString. A natív Delphi stringek referencia számláltak. A compiler belsőleg a Windows API/COM hívások megkönnyítésére támogatja a stringek PChar, PWideChar, illetve WideString típusokká történő kasztolását. Delphiben nincs nil string, vagy üres string van, vagy nem. A string hosszát ugyanúgy egy beépített Length() függvény adja vissza.

A Delphi RTL viszonylag elég sok string művelettel kapcsolatos függvényt definiál, névben szinte teljesen azonos a .NET String osztály metódusainak nevével.

A string kulcszó (típus alias) .NET-ben a System.String típust jelöli. .NET-ben a string típus egy 16 bites unicode string. A stringek csak olvasható adattípusok, nem módosíthatók. A string szolgáltat egy alapértelmezett indexer tulajdonságot, engedve hozzáférni minden egyes karakterhez (de csak olvasható). Az index zero alapú (mint minden FCL metódus, amely stringekkel dolgozik). A .NET különbséget tesz egy nil string és egy üres (zero-hosszúságú) string között. Fájdalommentessé téve ezt, a compiler rendelkezésre bocsájt egy length() intrinsic függvényt, amely 0-át ad vissza abban az esetben, ha a referencia nil, vagy ha ténylegesen nulla hosszúságú a string. .NET-ben a string lehetővé teszi ezt az ellenőrzést egy statikus metódus által is:

if (s <> nil) and (s <> '') then //...
if length(s) > 0 then //...
if not String.IsNullOrEmpty(s) then // ...

A String osztály viszonylag sok taggal szolgál a tipikus string műveletek teljesítéséhez, úgy mint: Pos, Copy, stb. Mivel a stringek csak olvashatók, ezek a metódusok generálni fognak egy új string példányt visszatérési értékként. Tehát a string egy immutable (megváltoztathatlan) típus.

A System.Text.StringBuilder osztály használható elérni a változtathatóságot, és erősen ajánlott is hasznáni azt, elkerülve a sok konkatenáció műveletet a ‘+’ operátor által. Persze ez se vehető mindig kézpénznek, érdemes megvizsgálni a kódot, hogy éppen mi a hatékonyabb.

A Delphi a megszorításokat a generikus típusdefiníció részeként definiálja.

type
  TStack<T, U: class; V: constructor; Z: IEnumerable<T>> = class

Ezzel van is problémám:

1. Curiously recurring template pattern (Nem lehet definiálni rekurzív generikus típusokat)
2. Nem lehet kölcsönös megszorításokat tenni a típusparaméterekre:

TMyClass<TA, TB: TA> = class end; // Ez okés.
TMyClass<TA: TB, TB> = class end; // Ez nem okés.
A Delphi Prism a where kulccszót használja generikus megszorítások definiálására.

type
  TStack<T, U, V, Z> = class
  where U is class,
        V has constructor,
        Z is IEnumerable<T>;
resourcestring
  RES_APPLICATION_TITLE = ‘My Application’;
A Delphi Prism nem támogatja a resourcestring nyelvi fícsört.
procedure és function kulcsszavak A Delphi Prism visszafele kompatibilitást ajánl ezekhez a kulcsszavakhoz, de bevezet egy új method kulcsszót is, függetlenül attól, hogy van-e visszatérési értéke a metódusnak, avagy nincs. Ajánlott használni az új kulcsszót minden újonnan írt kódhoz.
A Delphi támogatja az elnevezett konstruktorokat, más kérdés, hogy nem tanácsolt a használata, mert a C++Builder azokkal nem tud mit kezdeni. A natív Delphiben létezik a virtuális konstruktor fogalma. Ezekre nincs szükség .NET-ben, mivel a CLR automatikusan kikényszeríti az örökölt konstruktor hívást még mielőtt bármihez is hozzá lehetne nyúlni. A .NET runtime nem támogatja az elnevezett konstruktorokat és a Delphi Prism emiatt szintén kikényszeríti a névtelen konstruktor szintaxist. Ugyan megengedett kírni a Create-et a constructor kulcsszó után, de nincs hatással a generált kódra. A konstrukorok egyedien azonosítottak kell legyenek a paramétereik által.

Továbbá, a CLR-nek szükséges, hogy minden konstruktor meghívjon egy másik konstruktort, ami definiált ugyanabban az osztályban, vagy egy bázis osztályban, pontosan egyszer, mielőtt engedne hozzáférni a Self (this), vagy bármelyik tagjához a típusnak. Ahol szükséges, a Delphi Prism automatikusan befogja szúrni a hívásokat az örökölt konstruktorokra.

A Delphi nem GC alapú nyelv, így szükség van destruktorokra, amelyben a lefoglalt erőforrásokat fel kell szabadítani. Ugyanakor a Delphi támogat egy tulajdonos patternt, ami lehetővé teszi, hogy egy tulajdonosra (Owner) bízzuk az objektum megsemmisítését. Minden TComponent osztály rendelkezik ezzel az Owner property-vel, illetve alapértelmezésben minden TComponent származék konstruktora magába foglalja ezt paraméterként, mint pl. egy sima form is:

TMyForm.Create(Application).Show;
A Delphi Prism nem támogat destruktorokat, vagy a destructor kulcsszót. Ez a koncepció nem létezik a CLR-ben. Legtöbb esetben az osztályok tisztán menedzseltek és nincs szükség speciális kódra felszabadítani, vagy megsemmisíteni őket. Osztályok, melyek használnak unmanaged erőforrásokat (úgy mint fájl kezelők) és szükséges determinisztikus megsemmisítés, a runtime szolgál kétfajta koncepcióval, melyek teljesen támogatottak a Prism által is.

– Az egyik az IDisposable interfész megvalósítása, amely a Dispose pattern megvalósítása a .NET-ben, így lehetővé téve az objektumoknak, hogy engedjék el az unmanaged erőforrásokat. Néhány helyen ezt az FCL osztályok implementálják és elvárják, hogy illően legyenek felszabadítva. A könnyű használatot elősegítve használható a using kulcsszó.

– Finalizers: szolgáltat egy visszacsatolási mechanizmust megtisztítani osztályokat, amik nem lettek illően felszabadítottak. Ezek költségesek a GC-nek (előfordulhat, hogy egy objektumnak többször is át kell mennie a szemétgyűjtésen), emiatt csak abban az esetben kellene alkalmazni őket, ha tényleg szükséges.

Delphi Prism alternatíva a FreeAndNil() függvényre a DisposeAndNil(), amely ekvivalens a következővel:

if Assigned(AnObject) and (AnObject is IDisposable) then
  AnObject.Dispose;

AnObject := nil;
Delphiben lehetőség van inicializálni és megsemmisíteni erőforrásokat az alkalmazás betöltésekor, illetve kilépéskor. Erre szolgál az initialization, illetve finalization szekció. A Delphi Prism nem támogatja az initialization és finalization szekciókat, mert ez a koncepció nem létezik a .NET-ben és a CLR-ben. Az inicializáció osztály-konstruktorokkal (statikus konstruktorokkal) szintén elérhető.
Delphiben az események alapvetően tulajdonságai a típus metódus pointernek és csak egy kezelőjük lehet. A .NET másfajta esemény modelt használ, mint a Delphi. Delphi Prismben az események különálló típusai az osztálytagoknak, amik definiáltak az event kulcsszó által. Az események a delegatektől (multi-cast delegates) függnek, definiálják a szignatúráját az eseménynek, amelyeket ők reprezentálnak, illetve karbantartják egy listáját a feliratkozottaknak. További részletek itt.
A Delphi támogatja sima konstansokat, melyek állandóak:

const A = 10;

Illetve támogat ún. tipizált konstansokat, melyek módosíthatók, viszont függvényen belül deklarálva is megtartják az értéküket (emlékeim szerint, C/C++-ban static változók):

const A: Integer = 10;
A Delphi Prism két helyen alkalmazza a sztenderd Pascal hozzárendelő operátort, ahol a  Delphi sima ‘=’ operátort használ.

1. Default értékei a metódus paramétereknek:

method Foo(a: Integer; b: string := 'default')

Az oka ennek az, hogy ez jobban szemlélteti azt, hogy itt tényleg egy hozzárendelés történik, nem pedig egyezőség vizsgálat.

2. Hasonlóan, a mezők is lehetnek előinicializáltak.

myField: Integer := 5

Nem keverendő össze a natív tipizált konstansokkal (const A: Integer = 10), ahol az érték később is változtatható. Prismben sima konstansok vannak csak, amelyek csak olvashatók.

A Delphi teljes mértékben támogatja a pointer aritmetikát: További részletek itt. Mint menedzselt környezet, a .NET és a CLR limitált támogatással szolgál a pointerekhez, illetve memória manipulációhoz. Általában ez nem probléma, és a legtöbb kód ami pointerekre hagyatkozik újraírt lehet teljesen menedzselt kódban, ellenőrizhetően, egy kisebb erőfeszítéssel. Ez az ajánlott megközelítés.

Bárhogy is legyen, a CLR és a Delphi Prism támogatja unsafe kód beágyazását, megjelölve egy metódust az unsafe direktívával. A nem biztonságos nem jelenti azt, hogy eredendően rossz a kód vagy hibás, csak azt, hogy memória műveleteket végezhet, amik nem ellenőrizhetők, mint “bizonságos kód” a runtime által.

A Delphi enged beágyazni inline assembly kódot. A Delphi Prism IL kódot generál, nem x86, vagy valamilyen CPU specifikus kódot, így nincs támogatás asm betétek készítéséhez.
A Delphi az ‘=’ operátort használja, hogy implementáljon interfész metódusokat különböző néven, elkerülve a névütközéseket.

procedure MyFooBar;
procedure IFoo.bar = MyFooBar; // Maps the MyFooBar method to the IFoo interface.
A Delphi Prism nem szolgált szintaxissal ehhez, de használja az implements kulcsszót, hogy elérje ugyanezt, illetve sokkal több flexibilitással szolgál a folyamatban. További részletek itt.
Delphiben minden ‘/’ általi osztás művelet egy lebegőpontos értéket ad vissza. A div operátor használt az egész osztáshoz. A viselkedése a ‘/’ operátornak különbözik a Delphitől abban, hogy a Prism kikövetkezteti az eredmény típust a két operandusoból. Inkább, minthogy állandóan floating point típust használjon visszatérésként => Két egész érték osztása egy kerekített egésszé fog válni.
private
A szimplán privát tagok láthatók ugyanazon osztályon, de a uniton (egy forrás fájlon) belüli egyéb osztályok számára is.

strict private
Megfelel a C++ private láthatóságnak. Ezen tagok ténylegesen csak az adott osztályon belül láthatók (még uniton belül is), más osztályok számára nem.

protected
A védett tagok láthatók ugyanazon osztályon, a leszármazott osztályokon, illetve a uniton (egy forrás fájlon) belüli egyéb osztályok számára is.

strict protected
Megfelel a C++ protected láthatóságnak. Ezen tagok láthatók ugyanazon osztályon, a leszármazott osztályokon belül, de a uniton belüli egyéb osztályok számára nem.

public
Ezek a tagok minden osztály számára láthatók.

published
Ezek a tagok minden osztály számára láthatók. A különbség annyi a public-hoz képest, hogy a fordító ezeket a tagokat RTTI (meta információkkal) látja el, így reflectionözhetők. A Delphi Form-okat leíró DFM formátum is ezeket a tulajdonságokat szerializálja, deklaratívan leírva a UI-t, akárcsak a WPF a XAML-el.

private
Különbözik a Delphitől. A privát tagok láthatóak csak ugyanazon az osztályon belül, de más osztályon belül ugyanabban a forrás modulban nem. Ez összehasonlítható a Delphi strict private láthatóságával.

protected
Különbözik a Delphitől. A védett tagok láthatóak csak a leszármazott osztályokból, de más osztályon belül ugyanabban a forrás modulban nem. Úgyszint, a védett tagok láthatók csak ugyanazon példányán az osztálynak akihez ők tartoznak. Míg az osztályok hozzá tudnak férni privát tagjaikhoz egyéb példányainak ugyanannak az osztálynak, ez nem igaz a védett tagokra.

assembly and private
Ugyanaz, mint a private, csak annyi megszorítással, hogy csak abból az assembly-ből látszanak, ahol deklaráltak.

assembly
Bármilyen osztály számára hozzáférhető tagok, de csak az adott assembly-n belül, ahol deklaráltak.

assembly and protected
Ugyanaz, mint a protected, csak annyi megszorítással, hogy csak abból az assembly-ből látszanak, ahol deklaráltak.

public
Ugyanaz, mint a Delphi-ben. Ezek a tagok minden osztály számára láthatók.

A Delphi csak a konstansokat engedi kód blokkban inicializálni (a Prism nem engedi ezt):

const p: TPoint = (x: 15; y : 16);
A Delphi Prism használ elnevezett paramétereket inicializálni egy rekordot (struct) vagy osztályt.

var x := new MyRecord(Field1 := 'test', Field2 := 15.2);
A Delphi támogatja a megjelölését egy unitnak, egy metódusnak, vagy sima globális függvények/eljárásnak, mint deprecated. A compiler erről egy figyelmeztető üzenetben tájékoztat.

procedure TestMethod; deprecated;
A Delphi Prism nem támogatja a deprecated tag direktívát, de az obselete attribútum hasonló:

[Obsolete]
method TestMethod;

Röviden ennyi. Van rengeteg dolog ami ugye nincsen natív Delphibe, mint pl. típuskövetkeztetés, lambdák, LINQ, de még inline deklarált változók sem.

Hozzászólás