Delphi címkéhez tartozó bejegyzések

Delphi Prism – Új kifejezés típusok

A Pascal nyelvtanban a legalapvetőbb komponens ami egy metódus testet alkot, egy statement (“állítás”). Egy metódus test listája a statementeknek, elválasztva pontosvesszővel egymástól őket. x := 5 egy statement, akárcsak a MyObject.DoSomething. A begin/end blokk szintén egy statement, ami többek között saját magába is beágyazható, illetve tartalmazza egy listáját további statementeknek.

Van egy speciális típusa a statementeknek, amik elég gyakran használtak, ezek pedig a kifejezések (expressions). Egy kifejezés egy statement, aminek van egy értéke és így felhasználható bármilyen kód konstrukcióban ami elvár egy értéket, úgy mint jobb oldalán egy hozzárendelésnek, aritmetikai formulákon belül, vagy mint paraméter egy másik metódusra például. Az 5 egy kifejezés, így a MyObject.ToString is.

A korai Pascal nyelvekben (még a Delphi előtt), a kifejezések nem kellett, hogy full statementek legyenek. Például, a függvények (eljárások, amik visszaadnak egy értéket) nem voltak hívhatóak a visszatérési érték használata nélkül. Más szóval, például az sqrt(9) önmagában nem volt egy érvényes statement, de az x := sqrt(9) igen. Ez egy dolga a múltnak.

Funkcionális programozás

Na hova akartam én mindezzel kijukadni? Nos, az eljövő Spring 2010-es kiadásával a Delphi Prism-nek a fejlesztők kiterjesztették a nyelvet. Van három közösen használt statement, melyet innentől fogva mint kifejezéseket fog kezelni a fordító. Ezek a lépések szükségesek voltak ahhoz, hogy több funkcionális programozási koncepció kerülhessen a nyelvbe. A funkcionális programozás egy egészen különböző paradigma, és mint kiderült nagyon jól használható hatékony többszálú kód írásához (lsd. Erlang vagy Haskell). A Microsoft is fokozta a munkát az F#-on, ami a .NET-hez készített első saját funkcionális nyelvük lesz, melyet a VS 2010 is tartalmazni fog többek között.

A Prismhez a fejlesztők egy kissé másfajta megközelítést választottak. Kiterjesztik a nyelvet további funkcionális nyelvi elemekkel, de a cél továbbra is ezek síkban tartása a tradícionális Pascal szintaxissal és érzéssel. Az F#-nak például van néhány érdekes koncepciója ami erőteljessé teszi a nyelvet, de istentelen és közel érthetetlen szintaxis.

A következő új kifejezés típusok az első lépések ebbe az irányba Delphi Prism részről:

“if” kifejezések

Az “if” statement engedi a feltételes futtatását egyik vagy másik statementnek, egy boolean feltételtől függően. Ez gondolom idáig nem új:

if Condition then
  ThenStatement
else
  ElseStatement;

A kulcsszó itt a statement. Mind a then és az opcionális else ág tartalmaz egy statement-et (nem egy kifejezést!). A teljes “if” kikötés egy statement (de egy kifejezésen). A fejlesztők most kiterjesztették ezt a szintaxist, engedve a teljes “if” kikötést kifejezéssé válni:

if Condition then
  ThenExpression
else
  ElseExpression;

A teljes konstrukció egy kifejezés, reprezentálva az értékét a ThenExpression vagy ElseExpression-nek. Ennek következtében például a következő szintaxis a jövőben teljesen érvényes:

var s := 'The Condition is ' + if Condition then 'true' else 'false';

A típusa az “if” kifejezésnek (jelen esetben string) a két kifejezés legkisebb közös nevezője által kerül meghatározásra. Amenyiben az opcionális else ág hiányzik, az eredmény egy false feltételhez nil lesz, illetve ha a then kifejezésnek a típusa egy érték típus, akkor az “if” kifejezés egy nullázható típus lesz. Példa:

var x := if Condition then 3;

Az “x” egy nullázható Int32 típus lesz és tartalmazni fogja a 3 vagy nil értékek egyikét a feltétel teljesülésétől függően.

Egyesek most biztos bekeverik ide a C# conditional operátorát (?:), ami egyébként Delphi Prism-ben szintén van iif néven, de ez nem arról szól, hanem magáról a C# if statementjéről is, ha úgy tetszik. Persze az kérdés, hogy a C#-ot mennyire akarják elvinni funkcionális irányba.

“case” kifejezések

Ugyanez a kiterjesztés vonatkozik a “case” statementekre szimmetrikusan. Egy case statement, ahol minden egyes eset egy kifejezés, önmaga is egy kifejezéssé válik. Mint korábban az “if” kifejezésnél, a típus a tartalmazott kifejezések legkisebb közös nevezője által kerül meghatározásra, illetve ha nincs else statement, akkor nullázható lesz:

var s := case Number of
           1: 'One';
           2: 'Two';
         else
           'Many';
         end;

A lényeg valójában az, hogy a kifejezések csak altípusai a statementeknek, melyeknek van egy értékük. S persze ha jobban belegondolunk a fentiekbe, akkor valóban látszik, hogy semmi ok nincs azt gondolni, hogy a fenti statementeknek/kifejezéseknek nem kellene legyen értékük, ezért is innentől kezdve ezek önmagukban is véve kifejezések a nyelvben.

“for” kifejezések

Az utolsó új típusa a kifejezéseknek a “for” kifejezések:

for each x in SomeSequence yield Expression;

és

for x := StartValue to EndValue yield Expression;

Elsőre ránézésre az tűnhet fel, hogy a do kulcsszó ki lett cserélve egy yield-el. Ez főként azért van így, mert a yield-nek sokkal több értelme van egy kifejezés kontextusában. A do inkább futtatásra utal, míg a yield inkább értékre utal minden ciklus iterációban. Ha ez valakinek ismerősen hangzik a Delphi Prism iterátorainál megszokottakhoz, akkor az nem véletlen. Egy “for” kifejezésnek nagyon hasonló tulajdonságai vannak, illetve iterátori viselkedése, így nevezhetjük őket akár “anonymous iterators”, azaz névtelen iterátoroknak is.

Tehát mi egy értéke egy “for” kifejezésnek? Az értéke egy “for” kifejezésnek egy szekvencia, tartalmazva minden egyes kifejezést. Példa:

var SomeNumbers := sequence of Int32; // IEnumerable<Int32>
SomeNumbers := for i: Int32 := 0 to 100 yield i * i;
// SomeNumbers tartalmazza 0-tól 100-ig az egész számok négyezeteit

Pont mint egy iterátor, ez a szekvencia is on-the-fly lesz generálva, amikor felsorolttá válik. A fenti példában nincs kód ami futna, hogy kalkulálja a számokat 0 –tól 10000-ig. A “for” loop maga egy O(1) művelet. Ez nincs addig, amíg hozzá nem férünk a szekvenciához, például egy másik for each loop használata által. Újra:

var AllInts := for i: Int64 := 0 to Int64.MaxValue yield i;

Ez például egy szekvenciájává válik minden pozitív Int64 értéknek, de a tényleges hozzáférés csak itt következik be:

for each x in AllInts index i do
begin
  if i <= 1000 then break;
  Console.WriteLine(x);
end;

Ezek az új kifejezéstípusok is már részét képezik az új Delphi Prism 2010 “Spring”-nek, ami hamarosan értkezik a Visual Studio 2010-el.

Delphi Prism – Új szintaxis a bővítő metódusokhoz

Amióta be lettek vezetve a bővítő metódusok (extension methods) a .NET 3.5-ben, a Prism-es srácok folyamatosan agyaltak azon, hogy mi le lenne a legbarátságosabb módja támogatni azt. A C# egy elég kínos szintaxist használ, ahol a this kulcsszót használja az első paraméter leírásában. Ez egy kicsit megtévesztőnek is tűnhet. Többek között azért, mert ez egy C szerű dolog (homályos szintaxisa van, ami nem igazán felderíthető, illetve éretelmezhetetlen, hasonlóan a C++ =0 absztrakt metódus deklarációs szintaxisához), másrészt pedig a tény, hogy a metódusok fogadnak egy self (C#: this) referenciát, mint első paramétert, egy implementációs részlete kellene legyen a nyelvnek (vagy runtime-nak), nem egy szintaxis fícsör.

Egyik másik ötletük az volt a srácoknak, hogy egyszerűen engednek egy új extension direktívát hozzáfűzni a metódus deklárációhoz (hasonlóan a virtual kulcsszóhoz). De ezt szintén helytelen megközelítésnek érezték.

Ezen okból kifolyólag, idáig nem szolgáltattak semmilyen speciális szintaxist az extension metódusokhoz, így az embereknek kell “manuálisan” definiálni bővítő metódusokat, alkalmazva a megfelelő attribútumokat. Mivel időközben kiderült, hogy a bővítő metódus, mint fícsör elég gyakran használt, sikerült a srácoknak kötni egy elég jó kompromisszumot. Bizonyos szinten az ötletet a Google új Go nyelve szolgáltatta. Bár a nyelv maga elég C/C++ szerű és nem éppen tetszetős, mégis bevezet néhány szép koncepciót, mint pl. azt, hogy milyen egyszerű vele deklarálni új metódust egy osztályhoz.

Így a srácok úgy gondolták, hogy valami hasonló módon kellene dolgozzon a bővítő metódus a Prism-ben is. Egy bővítő metódus nem feltétlenül kellene, hogy része legyen egy ebből a célból létrehozott osztálynak, amivel tulajdonképp az égvilágon semmit nem csinálunk azon kívül, hogy bővítőmetódusokat adunk meg rajta. A bővítőmetódusok kikényszerítése egy osztály szintaxisba nem illő objektum orientált viselkedés, épp ellenkezőleg, ez egy visszaélés az OO szintaxissal. Sokkal egyszerűbb lenne haladni és deklarálni a metódust anélkül, hogy mindenféle kellemetlen szintaxist vagy szabályt megkelljen tanulni. Ennek köszönhetően megszületett az új koncepció, ami már az új Delphi Prism 2010 “Spring” Beta-ban is használható:

extension method Int32.Twice: Int32;
begin
  result := self * 2;
end;

Az új ebben az extension kulcsszó, viszont ez továbbra is úgy néz ki, mint egy szabályos metódus az Int32 típuson. Nincs szükség definiálni self paramétert, hanem pontosan úgy néz ki az egész, mint egy sima metódus deklaráció bármelyik osztályon a típus prefixel a metódusnév előtt. Egyedül csak az extension kulcsszó, ami jelöli azt, hogy az Int32 valójában nem a saját osztályunk, illetve azt, hogy kiterjesztünk egy osztályt, ami definiált valahol teljesen máshol.

Ez az új szintaxis sokkal kényelmesebbé és vonzóbbá teszi bővítőmetódusok definiálását, amikor és ahol szükségünk van erre, mindenfajta új osztálydeklarációs overhead nélkül.

Természetesen ez dolgozik komplexebb típusokon is. Pl. a következő kis program kiírja minden szám négyzetét 0 és 100 között:

namespace ExtensionMethods;
 
interface
 
uses
  System.Collections.Generic,
  System.Linq;
 
extension method IEnumerable<Int32>.Squared: IEnumerable<Int32>;
 
implementation
 
extension method IEnumerable<Int32>.Squared: IEnumerable<Int32>;
begin
  result := for each value in self yield value * value;
end;
 
begin
  var values := for i: Int32 := 0 to 100 yield i;
 
  for each i in values.Squared do
    Console.WriteLine(i);
end.

Ez csak egy újdonság az új Delphi for .NET 4.0 képességekből. Lesznek még nagyon nagy meglepetések!

Delphi Win32 – Call Stack

Mondanom se kell, hogy milyen hasznos tud lenni, ha nemcsak annyit mond nekünk a felhasználó, hogy mi a hiba üzenet, hanem megkapjuk a teljes hívásverem kivonatot pontos leírással, hogy melyik forrásfájlban és melyik sorban dobódott a kivétel. Az új Delphi 2010-be emlékeim szerint már van lehetőség kinyerni ezeket az információkat, viszont mivel én még a régi Delphi 2006-al dolgozok, szükség volt egy pici segítségre. Van egy hasznos kis eszköz, az a neve, hogy MemCheck (innen letölhető) és lehetővé teszi, hogy anélkül, hogy TD32 debug információkat, stack frameket, stb. fordítanák a kódba, a generált .map fájlból építse fel a hívási lánc folyamatát, szöveges, olvasható formában. Ehhez mindössze az alkalmazás mellé kell másolni a JclDbgMapOfAddr.dll fájlt, illetve a linker fülön be kell kapcsolni a .map fájl generálást (Detailed):

Delphi Linker - Map File
Ezután meglehet hookolni az RTLUnwind hívásokat, így közvetlen a kivétel közelében lehet kinyerni a call stack információkat:

type
  PNcExceptionRecord = ^TExceptionRecord;
  TNcExceptionRecord = record
    ExceptionCode: LongWord;
    ExceptionFlags: LongWord;
    OuterException: PNcExceptionRecord;
    ExceptionAddress: Pointer;
    NumberParameters: Longint;
    case Boolean of
      True:  (ExceptionInformation: array [0..14] of Longint);
      False: (ExceptAddr: Pointer; ExceptObject: Pointer);
  end;

var
  NcOldRTLUnwindProc: procedure; stdcall;

procedure HookedRTLUnwind; stdcall;
var
  ExceptionPointer: PNcExceptionRecord;
  CallStack: TCallStack;
begin
  try
    asm // Set the exception pointer.
      mov eax, dword ptr [EBP + 8 + 13 * 4]
      mov ExceptionPointer, eax
    end;

    // 4 last entries seem to be unusable.
    FillCallStack(CallStack, 4);
    // Build a call stack list.
    NcCallStackStr := 'Exception code: ' + IntToStr(ExceptionPointer.ExceptionCode) + sLineBreak +
      'Exception flags: ' + IntToStr(ExceptionPointer.ExceptionFlags) + sLineBreak +
      'Number of parameters: ' + IntToStr(ExceptionPointer.NumberParameters) + sLineBreak +
      TextualDebugInfoForAddress(Cardinal(ExceptionPointer.ExceptionAddress)) + sLineBreak +
      CallStackTextualRepresentation(CallStack, EmptyStr);
  except
    on E: Exception do
      Application.ShowException(E);
  end;

  asm
    mov esp, ebp
    pop ebp
    jmp NcOldRTLUnwindProc
  end;
end;

initialization
  NcOldRTLUnwindProc := RTLUnwindProc;
  RTLUnwindProc := @HookedRTLUnwind;

finalization
  RTLUnwindProc := @NcOldRTLUnwindProc;

Ha ez idáig ok, akkor az Application.OnException eseményre feliratkozva meglehet jeleníteni a CallStackStr tartalmát. Több okból is jobb az OnException eseménykezelőre bízni azt. Először is, mert az lefogja cserélni teljesen az OS fault messagebox-ot, másrészt az EAbort és azon kívételeket amelyeket elkapunk az alkalmazásba nem lesznek megjelenítve. Tehát kitűnően cserélhető vele a megjelenített üzenetablak. Amennyiben ezt is az RTLUnwindProc-ra bíznánk – ahol a világon minden kivétel jön, az elkapottak is – akkor az igen fájdalmas tudna lenni. Íme, hogy néz ki ez a jelenlegi alkalmazásban:

Call Stack
Szépen látható, hogy melyik forrásfájlban, hanyadik sorban történt a kivétel. Szerencsére a MemCheck FillCallStack engedi meghatározni, hogy mennyi hívás legyen kihagyva a listából (jelenleg 4), mivel ugye míg eljut a FillCallStack-ig történik még pár hívás. Aztán ezt szépen el lehet küldeni egy belső adatbázisba, mailben, stb. A lényeg, hogy pontosan tudni lehet hol történt valami gond. Még annyit a végén, hogy a JclDbgMapOfAddr.dll nem szükséges, ha TD32 debug információkat fordítunk az alkalmazásba. Viszont jóval nagyobb lesz a kód miatta és lassabb is.

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.

Delphi Prism – Mit tud, amit a C# nem?

Ha próbálta már használni valaki élesben mindkettőt, akkor tudja igazán mérlegelni, hogy melyikkel egyszerűbb és rövidebb kódot írni. Lehet a sok begin…end idegesítő egyeseknek, de azon kívül nincs olyan feature, ami egy fokkal kevesebb értékűvé tenné a C#-al szemben, épp ellenkezőleg… Egyszer írtam már egy hasonló összehasonlítást, de most kiegészítem még néhány dologgal:

Named indexed properties A C# csak a default indexelt tulajdonságokhoz tud hozzáférni. Delphi Prismben te tudsz definiálni és használni egyéb indexelt tulajdonságokat is a nevük által.
Sets Egy halmaz egy kollekciója azonos típusú ordinálás értékeknek. A halmaz kényelmes módja tárolni és hozzáférni értékekhez, melyek összetartoznak. További részletek…
Parallel Support

Ellátva az async kulcsszóval egy metódust az automatikusan egy külön szálon fog futni. Ha a metódus visszaad értékeket, akkor az egy Future-é fog válni.

Colon Operator Ez alapvető a mi nullázható típus támogatásunkhoz, de ugyanúgy meglepően hasznos fícsör az általános kódoláshoz. Alapvetően ahelyett, hogy használnánk a ‘.’, hogy hozzáférjünk tagokhoz és kapjunk egy NullReferenceException-t, inkább használjuk a ‘:’ operátort és a hívás elhagyott lesz, ha a referencia nil, illetve nil-t fog visszaadni, ha szükséges visszatérési érték.

IDisposable(SomeObject):Dispose; // only call dispose if the interface is implemented.
x := Button:Parent:Parent:Parent // access the 3rd parent of a control, where any level might be nil.
Inline Property Accessors Definiálj egyszerű tulajdonságokat az osztály deklaráción belül extra setterek/getterek nélkül.

property Foo: string; // Local field is implied;
property Right: Integer read Left + Width; // Reader expression is provided in line.
Class Contracts 1. Publikus invariantok
2. Require, Ensure (pre/post conditions)
Ezt nem részletezem, mert van már .NET 4.0-ba is, csak nem a nyelvbe építve.
Class References Definiállj “class of X” típusokat és virtuális osztály-metódusokat, hasonlóan a Delphi for Win32-höz.
“implies” operator Ez a leghasznosabb operátor invariantokhoz vagy pre/post conditionökhöz. Alapvetően a második rész csak akkor szükséges, hogy legyen igaz, ha az első is az. Ha az első hamis, a teljes kifejezés igaz. Illusztrálva:

require
   assigned(Customer) implies Customer.CreditRatio > 5;

Ez fordul a következőbe:

require
   not Assigned(Customer) or (Customer.CreditRatio > 5);

C fejlesztőknek az Assigned nem biztos, hogy világos. Majdnem az, mint a <> nil, vagy != null, csak ez tulajdonképp egy compiler intrinsic függvény, ami automatikusan annak megfelelő szintaxist választ, hogy milyen típust vizsgál. Ha egy eventet, akkor pl. automatikusan @Click <> nil,de ez valójában csak a Delphi Win32-vel való kompatibilitás miatt van itt.

“case type of” Írj egy switch/case statement-et ami futtat különböző eseteket attól függően, hogy mi a típusa az adott objektumnak:

case type MyControl of:
  Edit: // do something
  Button: // do something else
end;
Property Notifications Ezek megmentek rengeteg felesleges kódolástól. Kapcsold a “notify” kulcsszót egy tulajdonsághoz, mint direktívát (mint pl. a virtual), és a compiler automatikusan generál minden infrastruktúrát és interfészt ami szükségelt a property notification-höz. (C#-ban neked szükséges implementálni gettereket/settereket, hozzáadni az interfészt az osztályhoz, stb.)
Enhanced Nullable Types Ellenben a C#-al, a Delphi Prism compiler ad teljes támogatást a nullázható típusokhoz. Te megtudod hívni az ő tagjaikat és használni őket kifejezésekben. Kiterjesztett támogatás van a nullázható kifejezésekhez. Ha például egy aritmetikai kifejezésnek bármely része nullázható, a teljes kifejezés nullázhatóvá válik. Amennyiben bármely rész null, a teljes kifejezés válik nullá.

x: nullable Integer;
y: Integer;
var a := x + y; // Result is a nullable Integer.
var b := 5 * x; // If x is null, so is b.
Improved “for each” loop
for each matching x: Button in Controls do // Only run loop for buttons.

// Use i to count; also type of x is inferred
// automatically from type of "Controls", if its a generic enum
for each x in Controls index i do
“locked” directive A locked direktíva engedi metódusoknak, eseményeknek és tulajdonságoknak, hogy legyenek deklaráltak impliciten thread safe-nek.
Extended Constructor Calls Állíts be tulajdonságokat, mint részeit a konstruktor hívásnak. Ez különösen hasznos, amikor nem rendeled hozzá az létrehozott objektumra hivatkozó referenciát egy lokális referenciához. Például:

Controls.Add(new Button(Width := 100; Height := 50));

Enélkül szükség lenne egy átmeneti temp változóra a gombhoz. Ez már lehetséges a C# 3.0-al is.

Boolean Double Comparison
if 0 <= x < Count then // ...
Iteration delegation A yield kulcsszó támogatja a delegációját az iterátornak egy második szekvenciába. További részletek…
Empty Methods Ellátva az “empty” direktívával egy metódus interfész deklarációját nincs szükség definiálni az üres metódus testet.
Exception Filters Kapj el kiválaszott kivételeket nem csak típus, hanem bármilyen egyéni feltétel alapján. (Ez a feature támogatt VB.NET által, de a C# által nem)
Raising/Firing Events Te megtudod határozni a láthatóságot az esemény kiváltáshoz/eltüzeléshez. A C#-ban csak az osztály, ami definiálja az eseményt tudja eltüzelni azt. Kívülről csak az add/remove handlerekkel tudod ezt megtenni. Viszont Delphi Prismbe tudod írni a következő kódot és a leszármazottak el tudják tüzelni az eseményt:

public
  event Foo: EventHandler protected raise;

Illetve a korábban már bemutatott feature lista: C# vs. Delphi Prism. Én kezdek odáig lenni ezért a nyelvért, tényleg fantasztikus. Tudom, fúúúúj Pááászkááál… 😀 Én meg krix-kraxoktól kapot frászt. Nem azt mondom, lazán átlátom a C# kódot is, de kinek a pap, kinek a papné. Mint ahogy más a németet kedvelni, én inkább az angolt, ezért is inkább szeretem a begin…end-et, mint a krix-kraxokat.

Delphi Prism – With matching…

Eldöntöttem, hogy teleszórom a blogot a Prism fícsöreinek bemutatásával, mert szerintem fogalma nincs sok embernek, hogy milyen cool dolgai vannak. Akinek meg nem tetszik a Pascal, az meg ne olvassa el, oszt csókulom a kacsóját. Az előzőekben bemutattam az aspektus orientált programozásból egy darabkát (szerintem fantasztikus gondolat volt megalkotni azt), most pedig bemutatok még egy hasznos fícsört, ami sok kódolástól mentheti meg az embert. A Prism minden egyes porcikája a monoton kódolási folyamatok lényeges egyszerűsítésére van kihegyezve. Egyik ilyen egyszerűsítés a with matching kulcszó páros. Mit csinál ez? Nézzük a következő kódot:

if MyObj is MyType then
begin
  var MyTypeRef := MyType(MyObj);

  MyTypeRef.Property := ...;
  MyTypeRef.Method(...);
end;

Ezt úgy ahogy van helyettesíthetjük a következővel, mivel a with matching automatikusan csinál egy <> nil (!= null) ellenőrzést és ha nem teljesül a feltétel, akkor átugorja a kódot:

with matching MyTypeRef := MyType(MyObj) do
begin
  lMyTypeRef.Property := ...;
  lMyTypeRef.Method(...);
end;

A matching kulcsszó eredetileg a for each loop-hoz volt bevezetve feldolgozni csak azon típusokat, amelyek megyeznek a meghatározott típussal, mint pl.:

for each matching element: string in myArrayList do
begin
  MessageBox.Show(element);
  ...
end;

Nem beszélve arról, hogy a with kulcsszó (ami C alapú nyelvekbe a világba nem volt soha), szintén hasznos némely esetben, persze néha vezethet problémákhoz, mint pl.:

with Button1, Button2, Button3 do
begin
  Text := ...;
end;

Ilyenkor nem az történik, hogy mindhárom Button Text tulajdonsága beállítódik, hanem az utolsónak lesz nagyobb prioritása. A fordító megnézi, hogy melyikben található meg a with scope-on belüli tag először a Button3-ból kiindulva visszafele, és megpróbálja ezalapján meghívni a megfelelő tagot a megfelelő objektumon. Amikor a Self (this) –nek is van egy Text tulajdonsága, akkor még érdekesebb lehet dolog, mert akkor a with scope-on belül csak így látszik önmaga Text tulajdonsága: Self.Text := …; Tehát ez azért okozhat problémákat is, de amikor 200 property-t kell beállítani egy objektumon nem erre gondolok. Ok, C# 3.0-ba már vannak objektum inicializálók, de azért az még nem ugyanez.

Delphi Prism – Aspektus Orientált Programozás (AOP) for .NET 4.0

A Delphi Prism-nek van egy új képessége, ami hozzáad aspektus orinentált fejlesztést lehetővé tevő infrastruktúrát a nyelvhez. Ennek az új infrastruktúrának a neve “Cirrus”. Az AOP lehetőséget ad nekünk arra, hogy megváltoztassuk a kód viselkedését, hozzáadjunk/eltávolítsunk mezőket, tulajdonságokat, eseményeket, metódusokat, vagy akár extra osztályokat speciális attribútum típusok  (aspektusok) alkalmazása által. Az aspektusok Prism-ben íródtak, egy különálló assembly-be fordítottak, és újrahasználhatók különböző projektek által. Meglehetősen egyszerű írni aspkektusokat. A fejlesztőnek mindössze létre kell hozni egy új osztálykönyvtárat, hivatkozni a RemObjects.Oxygene.Cirrus assembly-re, majd létrehozni egy új attribútum osztályt. Ez az attribútum osztály a System.Attribute osztályból kell származzon és kell legyen egy szabályos AttributeUsage() attribútuma annak jelölésére, hogy hol lehet alkalmazott. Az egyetlen különbség a szokványos attribútumokhoz képest, hogy az aspektusok implementálják egyikét a speciális interfészeknek, mint például IMethodImplementationDecorator.

Az aspektusok betöltődnek és inicializálódnak a compiler által fordítási időben és lehetőséget adnak arra, hogy erős hatást gyakoroljunk velük a compiler által generált kódra. A következő példában mi létrehozunk egy aspektust, amely kidekorálja a metódusait azon osztálynak, amelyre alkalmazzuk. Ezt az IMethodImplementationDecorator teszi lehetővé, amelynek egyetlen egyszerű metódusra van szüksége, aminek a neve HandleImplementation. A compiler fogja meghívni ezt a metódust miután egy metódus test (implementáció) generált, és engedi az aspektusnak, hogy befolyásolja a generált kódot, megváltoztatva vagy esetleg kiegészítve azt. Nézzük egy ilyen aspektus attribútumot:

namespace MyCompany.Aspects;

interface

uses
  RemObjects.Oxygene.Cirrus;
type
  [AttributeUsage(AttributeTargets.Class or AttributeTargets.Struct)]
  LogToMethodAttribute = public class(System.Attribute, IMethodImplementationDecorator)
  private
  protected
  public
    [Aspect:AutoInjectIntoAspectAttribute]
    class method LogMessage(aEnter: Boolean; aName: String; Args: Array of object);
    method HandleImplementation(Services: IImplementationServices; aMethod: IMethodDefinition);
end;
 
implementation
 
class method LogToMethodAttribute.LogMessage(aEnter: Boolean; aName: String; Args: Array of object);
begin
  if aEnter then
    Console.WriteLine('Entering '+aName)
  else
  Console.WriteLine('Exiting '+aName);
end;
 
method LogToMethodAttribute.HandleImplementation(Services: IImplementationServices; aMethod: IMethodDefinition);
begin
  if String.Equals(aMethod.Name, 'LogMessage', StringComparison.OrdinalIgnoreCase) then exit;
  if String.Equals(aMethod.Name, '.ctor', StringComparison.OrdinalIgnoreCase) then exit;
 
  aMethod.SetBody(Services, method begin
    LogMessage(true, Aspects.MethodName, Aspects.GetParameters);
    try
      Aspects.OriginalBody;
    finally
      LogMessage(false, Aspects.MethodName, Aspects.GetParameters);
    end;
  end);
end;
 
end.

A fenti kódrészletben a mi aspektusunk összehasonlítja a metódus nevét a “.ctor” és “LogMessage” stringekkel (mi nem akarjuk kiterjeszteni azokat), és ha ők nem egyeznek, akkor az eredeti metódust kiegészíti egy “LogMessage” hívással, ami védett egy try/finally blokk által.

Az “Aspects” osztály egy speciális compiler magic class, amit a Cirrus biztosít számunkra, engedve az aspektusnak, hogy átvegye az irányítását a kódnak amire alkalmazott. Többek között láthatjuk, hogy ő le tud kérdezni egy metódusnevet, paramétereket, de a metódus testét úgyszint, ami valójában írt az eredeti forrásában az osztálynak.

Hívva a SetBody()-t a metóduson, az aspektus kitudja cserélni a testét a generált kódnak (ebben az esetben véve az eredeti testet és körülhatárolni azt az én saját “LogMessage” hívásommal. Egyszerűen kódot injektál a metódus testbe kívülről. Megjegyzendő, hogy az új metódus-test adott, mint sima, olvasható Prism kód, egy kiterjesztés formájában a névtelen metódus szintaxisnak.

Érdemes megjegyezni, hogy a mi aspektusunk “LogMessage” metódusának szintén van egy aspektusa, ami saját magára alkalmazott. Az [aspect:AutoInjectIntoClass] egy aspektus, ami definiált a Cirrus által, és csak az aspektuson belüli használatra tervezett. A szerepe jelölni, hogy a metódus injektálva kell legyen az osztályba, amire az aspektus alkalmazott. Ez azért szükséges, mert a mi aspektusunk lehetővé teszi a “LogMessage” használatát új és megnövelhető (augmentált) metódus testben, de az is valószínű lehet, hogy nem létezik olyan metódus a cél osztályban. Az AutoInjectIntoClass nélkül minden logikát a LogMessage-hez szükséges lenne belezsúfolni a SetBody hívásba. Ez potencálisan keménnyé tenné az olvashatóságot, és potenciálisan duplázná a sok kódot és logikát.

Íme egy példa, hogy tudjunk használni a mi kis LogMessage metódus injektálónkat:

namespace CirrusTest;

interface

uses
  MyCompany.Aspects,
  System.Linq;
 
type
  [Aspect:LogToMethod]
  ConsoleApp = class
  public 
    class method Main;
    class method Test(S: string);
  end;
 
implementation
 
class method ConsoleApp.Main;
begin
  // Add your own code here
  Console.WriteLine('Hello World.');
  Test('Input for Test');
end;
 
class method ConsoleApp.Test(S: string);
begin
  Console.WriteLine('TEST: ' + s);
end;

end.

Mi egyszerűen létrehoztunk egy új konzol alkalmazást, ami hivatkozik a mi aspektus assembly-re. Nagyon fontos megjegyezni! Mivel az aspektus fordítási időben alkalmazott infrastruktúrális kiegészítés, a végső futtatható fájl nem függ az aspektus könyvártól (Cirrus) tovább. Tehát a kód injektálást a compiler fordítási időben maga végzi el, extra assembly már nem kell az .exe mellé!

Futtatva és debuggolva ezt a programot a következő eredményt kapjuk:

Entering Main
  Hello World.
  Entering Test
    TEST: Input for Test
  Exiting Test
Exiting Main

Mint látható a reflectorral is, a LogMessage metódus injektált a cél osztályba és a referencia szekcióba csak az mscorlib található, se a RemObjects.Oxygene.Cirrus.dll, se a mi aspektus assemblynk (MyCompany.Aspects) nem szükséges, hogy telepítve legyen az alkalmazás futtatásához.

 
Ettől még jóval többről szól ez az egész, de ebből már körvonalazódik szerintem, hogy mi történik. Szerintem ez egy szép Delphi infrastruktúrális nyelvi fejlesztés, amit jelenleg egyik .NET nyelv sem támogat még.

Delphi Prism – .NET 4.0 BigInteger

A februrári kiadása a Prism-nek már beépített támogatást tartalmaz az új .NET 4.0 BigInteger típushoz is:

var r := 6985743574365784365432758943759843265983427594356349776534765743928657843567843625437564392067452985674398563429;
var y := 8574398256943285643296524390;
Console.WriteLine(y);
Console.WriteLine(r);
y := r - y;
Console.WriteLine(y);

A kimenet:

8574398256943285643296524390
6985743574365784365432758943759843265983427594356349776534765743928657843567843625437564392067452985674398563429
6985743574365784365432758943759843265983427594356349776534765743928657843567843625428989993810509700031102039039

Minden konstans, ami nem fér el a legnagyobb típusban ami támogatott ((U)Int64), automatikusan BigInteger-é fog válni, vagy meghíusul a fordítás ha az új típus nem feloldható (korábbi .NET verziók). A BigInteger pontosan úgy viselkedik, mint egy hagyomás Integer típus, bár van néhány extra művelet, mint pl. GreatestCommonDivisor, Remainder, Pow(er) és konverziók Integer típusba és vissza.