Archívum

Archive for 2007. december

Bevezetés a C# 3.0 újdonságaiba (2.) – Object initializer-ek és anoním típusok

december 31, 2007 4 hozzászólás

A cél továbbra is a LINQ megismerése és elsajátítása, azonban ahhoz, hogy tudjuk használni ezt a kiváló technológiát, értenünk kell, hogy milyen egyéb nyelvi elemekre támaszkodik, hogyan működik. A linq működésének megértése elengedhetetlen követelmény, annak hatékony használatához.

Object Initializers

Ha C# 3.0-át megelőzően szerettünk volna egy osztályból egy példányt létrehozni, megfelelő paraméterekkel, akkor szükségünk volt egy megfelelően felparaméterezett konstruktorra. Ha a konstruktor nem volt képes minden számunkra szükséges tagváltozót beállítani, akkor kénytelenek voltunk property-k segítségével megtenni azt. Mostantól az object initializer-ek segítségével sokkal rugalmasabban tehetjük meg ugyanezt. Nézzük az alábbi osztálydefiníciót:

A fenti osztály konstruktora a PublishYear mezőt nem állítja be. Így ha azt be szerettük volna állítani, akkor azt egy külön hívással kellett eddig megtennünk.

Nézzünk néhány példát object initializer-t használva:

Az első példában az összes tagváltozónak értéket adtunk.  Tulajdonképpen a háttérben az object initializer implicit módon meghívta a default konstruktort, ezt követően beállította a mezők értékeit. Látható, hogy a szintaxis magáért beszél. A példányosítás során kapcsos zárójelek között kell megadni a mezőneveket, az értékadásokat, vesszővel felsorolva.

A második példában látható, hogy csak két tagváltozót állítunk be, az "Author" mező üresen marad.

A harmadik példában expliciten meghívjuk a nem default konstruktort. Pirospontért el lehet rajta gondolkodni, hogy mi történik akkor, ha a harmadik esetben nem csak a "PublishYear"-t, hanem a másik két tagot is beállítjuk, természetesen másik értékre, mint amit a konstruktornak adunk át.

Az object initializerek segítségével csak publikus tagváltozók és property-k állíthatók be. A háttérben egy temporary változóba inicializál egy példányt, majd ha minden tagváltozót beállított, akkor a referenciát átadja a tényleges változónak. Ez a technika nagymértékben növeli a kód olvashatóságát és komoly rugalmasságot csempész bele. Természetesen az object initializer-ek egymásba ágyazhatók.

A fent látható osztálydefiníciókban kicsit furcsának tűnhetnek a property-k. Ezek az ún. auto-property-k. Nincs szükség nekünk privát változót létrehozni, majd hozzá a szokásos property-ket definiálni. A "prop Tab-Tab-ot" követően létrejövő sor mögött ott a teljes arzenál, ami a működéshez kell. A privát tag (amit nem látunk), a property get és set definíciója (amit szintén nem látunk) is automatikusan elkészül. Az alábbi kódrészlet egy rövidke példa a beágyazott object initializer-ek használatára:

A második tag egy osztálypéldány lesz, aminek tagváltozóit szintén object initializer segítségével állítottuk be. Ez eddig csupán egyfajta szintaktikai édesítőszer. Ahhoz, hogy meglássuk az igazi erősségét ennek az újdonságnak, meg kell ismernünk először az anoním típusok világát.

Anonymous Types

Létrehozhatunk ún. Anoním típusokat, melyek komoly szolgálatot tehetnek nekünk, például linq lekérdezések használatatkor.

Íme egy egyszerű példa anoním típus használatára:

Látható, hogy a new kulcsszó után nem adtunk meg típusnevet, valamint object initializer segítségével adtuk meg és állítottuk be a tagváltozókat. A fenti definíció ekvivalens az alábbi osztálydefinícióval.

A var-ként deklarált anonymousType változó felveszi ezt az anoním típust. A compiler a következő típust készíti el nekünk. Igazi gyönyörűség! {Name = "<>f__AnonymousType0`2" FullName = "<>f__AnonymousType0`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"} Ebből az érdemi információ számunkra a típus neve: "<>f__AnonymousType0`2" , valamint a típus két tagváltozója, ami egy string, illetve egy int. Azaz nem kellett egy fölösleges segédosztályt definiálnunk, a compiler elkészítette nekünk, nem volt szükség fölösleges többlet munkára. Láthatjuk, hogy a var micsoda nagy segítséget nyújt számunkra. Nélküle ugyanis lehetetlen lenne az anoním típus használata. Hiszen milyen típusú változónak tudnánk értékül adni? (object már kasztolás) A var-ként definiált változó implicit módon felveszi ezt az anoním típust, és úgy használhatjuk, mint bármely hagyományos osztálypéldányt.

Zárszóként nézzünk egy egyszerű linq-s példát arra, hogy miként tudjuk ezt kihasználni lekérdezésünkben.

A fenti lekérdezés az  1996 után publikált könyvek között szűr. Ami fel kell, hogy tűnjön, az az, hogy nem "select b" van odaírva, azaz a query változóba nem könyvek egy listája kerül be!!! Hanem select new {…} miatt anoním típusok egy listájával tér vissza a lekérdezés. Az anoním típusunknak két mezője van, az "Author" és a "Title". SQL-es gondolkodással a select b tulajdonképpen egy olyan osztály-t szimbolizál, ami egy teljes sort képvisel. Míg a fent említett projekció "oszlopokat" vág ki nekünk a sorból és egy anoním típusba csomagolja az eredményt. Fárasztó lenne írni minden ilyen projekcióhoz egy megfelelő csomagoló osztályt, nem igaz? Helyette anoním típusok és object initializer-ek segítségével egy roppant rugalmas és átlátható megoldást tudtunk elkészíteni. Természetesen használhattunk volna származtatott értékeket is. Az alábbi példán csak a select rész látható:

Az új anoním típusunk egyik mezője a könyv címe (Title), a másik a "Price" nevű kalkulált mező pedig a bruttó ára lesz.

Lekérdezéseinknek innentől kezdve csak a képzelet, nomeg a józan ész szabhat határt. Remélem minden olvasó profitált vmit ebből a kis irományból. Végül pedig megragadnám az alkalmat és BOLDOG ÚJÉVET kívánnék minden kedves olvasónak!

Kategóriák:Uncategorized

WinForms – Hozzáférés control-okhoz külön szálból

december 18, 2007 6 hozzászólás

Bár nem mai technológia a WinForms, mégis sokak futnak abba a problémába, amikor külön szálból szeretnének hozzáférni az egyik control-hoz, programuk egy csúnya hibaüzenettel elszáll. Miért van ez?

Ha a winforms-os controlunkat több szálból is próbálnánk elérni és mindenféle veszélyesebbnél veszélyesebb dolognak kitenni (ha nem, akkor is) előfordulhat, hogy inkonzisztens állapotba hoznánk, ami megengedhetetlen. A UI külön szálon fut, ezért a .NET Framework csúnyán ránkszól, ha egy másik szálból szeretnénk hozzáférni, ami nem fog menni. Egészen pontosan illegal cross-thread operation hibaüzenetet dob nekünk. Természetesen létezik megoldás a problémára, méghozzá több is, én most az egyik legegyszerűbbet szeretném itt bemutatni.

Nézzük a problémát. Ez a kis program a gombnyomás hatására egy külön szálat indít, amely egy szöveges üzenetet ír bele a textBox-ba.

Munkánknak be is érik a gyümölcse, arra a sorra érve, ahol megpróbáljuk beállítani a Text property tartalmát a következő szeretetcsomagot kapjuk:

A probléma már nem ismeretlen számunkra, kérjünk hozzáférést a controlhoz. Egy lehetséges megoldás a következő: Hozzunk létre egy callback delegate-et, nézzük meg, hogy a textBox-unk InvokeRequired property-je true-e, és ha igen, akkor hívjuk meg a textBox Invoke függvényét a delegate-ünkkel, természetesen sajátmagunkat visszahívva. Ha false, akkor nyert ügyünk van, már írhatjuk is a textBox-ot.

Természetesen van más megoldás is Backgroundworker-ek használatával és egyéb varázslatokkal. De úgy gondolom a fenti megoldás magáért beszél.

Kategóriák:Uncategorized

LINQ – Bevezetés a C# 3.0 újdonságaiba (1.)

december 16, 2007 2 hozzászólás

A mai fejlesztői világot teljesen átitatja az objektum orientált gondolkodás, tervezés. Sajnálatos módon még ma is meg kell küzdenünk nem objektum orientált adatforrásokkal, mint például az XML, vagy a relációs adatbázisok. Ezt a kellemetlenséget igyekszik a linq elfedni előlünk. A Microsoft fejlesztői kitettek magukért, nyelvi szinten integrálták be ezt a rendkívül rugalmas és hatékony lekérdező nyelvet. Természetesen a gondolat nem újkeletű, gondoljuk csak az nQuery-re vagy az NHibernate-re. A fejlesztői világ reakciója természetesen nem egyhangú, vannak akik rögtön nekiálltak saját linq providerek fejlesztésének, (Linq 2 Sharepoint, Linq 2 Flickr, Linq 2 Google stb…). Hosszútávon mi is a cél? Gondoljunk bele, milyen kényelmes lenne, ha különböző adatforrásokhoz ugyanazt a lekérdező szintaktikát tudnánk alkalmazni, méghozzá C#-ból, vagy Visual Basic-ből. Mindegy, hogy egy SQL Servert vallatok, vagy egy xml fájlban keresek, netán egy Sharepoint listát dolgozok fel. (Aki írt már CAML-t az tudja, miről beszélek) De vannak olyanok is, akik szkepticizmussal teli pillantásokat vetnek rá, ugyanis a linq alaposan átírja az adathozzáférési rétegről(DAL) eddig alkotott elképzeléseinket.

Node itt az ideje, hogy mások véleménye helyett, saját elképzelést tudjunk alkotni a LINQ-ról. Ezt a technológiát a .NET 3.5 hozta el számunkra, mégpedig C# 3.0-ba illetve VB 9.0-ba ágyazva. Itt most én a C# 3.0-át választottam.

Az első, amit tudni kell, hogy egy linq lekérdezés bármely olyan adatforrásra rászabadítható, amely megvalósítja az IEnumerable<T> interface-t, azaz felsorolható. Az alábbi példában a PersonList tömbből azokat a stringeket válogatjuk ki, amelyeknek a hossza legalább 4. Ugye mennyire hasonlít az SQL-re? (Pirosponttért el lehet gondolkodni rajta, hogy vajon miért from – select a sorrend és miért nem select-from) Ezt követően a query elemeit egy mezei foreach-csel bejárhatjuk. Ezt a lekérdezés formát nevezik query expression-nek. Könnyen olvastható és érthető.

A fenti lekérdezést megírhattuk volna így is:

Ezt nevezzük method-based query-nek. A két lekérdezés teljesen egyértékű. Vizsgáljuk meg egy kicsit jobban ezt a második szerkezetet, ugyanis számos újdonságot rejt számunkra. Először is a PersonList objektum típusa string tömb! Nincs ilyen nevű tagfüggvénye, hogy Where, OrderBy vagy Select. Ezek az úgynevezett extension method-ok. "Később" definiált speciális statikus függvények, melyek kiterjesztik azon objektumok képességeit, melyekre alkalmazhatók. A fenti Where függvény egy extension method, melynek paramétere egy lambda expression. Ez nem más, mint egy egyszerű logikai kifejezés, ami a Where esetében azt mondja, hogy válaszd ki azokat a p-ket, melyekre igaz az, hogy a p hossza legalább 4. Első ránézésre talán kicsit elrettentő de valójában egyszerű dolgoról van szó. Amire érdemes figyelni, hogy ezekkel a nyelvi újdonságokkal, illetve a kód tömörségére törekedve hihetletlenül átláthatatlan kódokat lehet írni.

Még egy dologra térnék ki anélkül, hogy túl mélyre próbálnék hatolni a C# 3.0 nyelvi újdonságaiban, ez pedig az implicit módon típusos lokális változó, azaz a "var". Sokan fáznak tőle, mert hogy típus nélküliség .net-ben nem szerencsés. Ők tévednek, mert nem erről van szó. A var-ként deklarált változó igenis nagyon erősen típusos, csak éppen implicit módon adom meg a típusát. Azaz, az lesz a típusa, ami az értékadás jobboldalán áll.

Nézzük meg az alábbi képet. A debug módban futtatott Locals ablakban látható, hogy "a" string, "b" int, "c" pedig DateTime típusú. A változó inicializálásakor implicit módon megadtuk a típusát. Hogy ezt miért jó? Sokszor egyszerűbb, kevesebbet kell gépelni, de a legfőbb indok, hogy ha anonymous type példányt hozunk létre, akkor értékül csak var-ként deklarált változónak tudjuk adni, hiszen nincs neve a típusnak.

Remélem senkit sem rettentettem el a linq-tól és a C# 3.0-ától, sőt éppen ellenkezőleg, szeretnétek jobban megismerni ezeket az újdonságokat, megérteni, hogy működnek és mire jók. Ez a cikksorozat ebben próbál segítséget nyújtani.

Kategóriák:Uncategorized

Péntek esti WPF mosoly

december 14, 2007 5 hozzászólás

Így pénteken este jobb dologom nem lévén Krisztián kollegám kérdését próbáltam megválaszolni. Ha netántán valaki olyan elvetemült lenne, hogy dinamikusan szeretne hozzáadni elemeket egy ListBox-hoz, és történetesen ezek az elemek stringek és mondjuk ugyanazok a stringek, akkor a listbox bizony megbolondul. Miért van ez, és mit lehet tenni ellene? Különböző stringeknél ez miért nem jelentkezik?

Egészen konkrétan:

És akkor SingleSelect módban elkezdem klikkelgetni a listboxot, ami összevissza kiválaszt több elemet, annak ellenére, hogy single módban vagyunk, tiszta káosz az egész.

Amit tudok, az a megoldás:

Ha így adogatjuk hozzá az elemeket, akkor a dolog tökéletesen működik.

Az Add függvény objecteket vár, tehát nem arról van szó, hogy csak ListBoxItem mehet bele. Az én gyanúm az, hogy a listbox az elemeit a Hashkódjuk alapján különböztetheti meg. Mivel a GetHashCode()-ot a stringek esetén felüldefiniálták oly módon, hogy ha két string tartalma megegyezik, akkor a hashcode-juk is egyezzen meg, így a listbox nem igazán tud mit kezdeni a dologgal. A második esetben a stringeket lényegében egy másik osztályba wrappeltük, konkrétan ListBoxItem-ekbe, így a különbség tétel nem okoz problémát. A dolog elég nagy mosolyt csalt az arcomra, és ez továbbra is csak tipp. Bárkinek szívesen fogadom bármi ötletét, esetleg vki jól reverse engineeringelje meg és akkor majd megtudjuk jól 🙂

Szerk.

A sejtés helyes. WinForms-ban a ListBox az add függvény hívása esetén egy ListBoxItem objektumot hoz létre, így a wrappelés megtörténik. Wpf esetén viszont megőrzi az eredeti típust, így sztringek esetén a hashcode összeakadhat. Special thnx to R1cs1 and Giorgio!

 

Kategóriák:Uncategorized