Megoldás – Minifeladatok I.

Ez a cikk a Minifeladatok I megoldását tárgyalja. A program a SuperValue hibás tervezése és használata miatt a várt 100 helyett a 0 értéket írja ki.

A megoldás röviden

A SuperValue egy értéktípus. Az értéktípusú változók maguk hordozzák a reprezentált értéket. Amikor egy értéktípusú változó értéket kap egy másiktól, a teljes reprezentáció átmásolódik. A futtatórendszer a működése közben ideiglenes változókat hozhat létre. Egyik ilyen eset, amikor egy metódus visszatérési értékkel adja vissza a vezérkést a hívónak. A minifeladatban a List indexere (ami egy metódus) ezt teszi. A futtatórendszer az indexer által visszaadott értéket egy ideiglenes területre másolja. Ezután ezen az ideiglenes változón hajtódik végre az Increment() hívás, miközben a List által tárolt eredeti értékek érintetlenek maradnak.

A megoldás szemléletesen

Ha a fenti eszmefuttatás követhetetlen volt, segíthet megérteni a következő ábrasorozat. Az első ábra a List<SuperValue> példány állapotát mutatja inicializálás után.

A lista a halmon foglal helyet, belső ábrázolásában egy hagyományos C# tömböt használ (Items). Mivel a SuperValue értéktípus, a tömb “magába foglalja” a sok SuperValue-t. Ha a SuperValue referencia típus lenne, akkor a helyzet a következőképpen nézne ki:

Ahhoz, hogy megértsük mi történik a Minifeladat I kérdéses ciklusában, amelyben az Increment hívódik, meg kell ismerkedni picit részletesebben a .NET memóriamodelljének részeivel. Amikor egy metódust futtat a futtatórendszer, akkor annak a metódusnak fenntart egy Method State-et. (CLI Specifikáció 12.3.2). Ez a Method State tartalmazza, hogy a metódust milyen paraméterekkel hívták (Argument Array), ez tartalmazza a metódus lokális változóit (Local Variable Array), illetve tartalmaz egy úgynevezett kiértékelési vermet (Evaluation Stack), amin a IL kód műveleteket tud végrehajtani, például aritmetikai műveleteket végezni, vagy metódushívás előtt ide teszi a paramétereket.

A példaprogram Main() metódusának a Local Variable Array-e tartalmazza például a “values”, az “i” és a “sum” változókat. Ezen kívül tartalmaz még néhány ideiglenes változót, amit a C# compiler hozott létre. Az egyik ideiglenes változó típusa egyébként egy SuperValue. A helyzet a következő ábrán látható:

A példaprogram második ciklusában két fontos lépés történik. Az első lépés egy List<> indexer hívás. Az indexer az egy közönséges metódushívás, lényegében egy property getter, csak a C# fordító engedi meghívni a speciális szögletes zárójeles szintaktikával. Amikor ez a metódus meghívódik, neki szintén létrejön egy Method State. Ez leírja az átvett paramétereket (a “this” referencia, illetve hogy hanyas indexű elem kell), esetleges lokális változókat, illetve miután a metódus kódja lefutott, az Evaluation Stack-re teszi a visszatérési értéket, ami SuperValue típusú.

A List<>.get_Item a this paraméter alapján megtalálja a memóriában a lista példányt, amin dolgoznia kell. Ennek hozzáfér az items mezőjéhez, ami egy System.Array. Veszi az index argumentumot, ezzel elkéri a tömb megfelelő elemét. Mivel a metódusok a visszatérési értéket az Evaluation Stack-en keresztül adják vissza (IL illetve CLI szinten, nem a jittelt kód szintjén), a CLR a tömbből az adott SuperValue-t átmásolja az Evaluation Stack-re. Jegyezzük meg, hogy már ebben a pillanatban elszakadtunk a szándéktól, hogy az eredeti értéken dolgozzunk:

Amikor a hívott metódustól visszakerül a vezérlés a hívóhoz (tehát a get_Item-től a Main-hez), akkor a CLR a get_Item visszatérési értékét a get_Item Method State-jéből átmásolja a Main Method State-jébe. Ez eddig már a második másolás volt, így már két lépés távolságban vagyunk az eredeti, módosítani kívánt tömb-béli értéktől. Az értékünk most az Evaluation Stack-en van, viszont ez egy folytonosan változó munkaterület. Emiatt a C# fordító olyan kódot generál, ami ezt az értéket a Local Variable Array-ban egy munkaváltozóba menti el ($temp).

Ez az a pillanat, amikor meghívódik az Increment(). Ez this-nek a $temp-et kapja meg, így ennek az értékét növeli. Ezek alapján világos, hogy miért lesz a program kimenete 0: nem a List<> által tárolt értékeken dolgozik a program. De vajon miért működik ez az egész ilyen esetlenül? Ez egy kicsit hosszabb történet.

Mi az érték típus?

Tudjuk, hogy a .NET alatt vannak érték típusok és referencia típusok. Az érték típusok lényegét azonban elhomályosítja a referencia típusok szerencsétlen elnevezése. A fő különbség, illetve a fő ismérv nem az, hogy az egyik esetben a változók a “példány” értékét hordozzák, a másik esetben pedig egy referenciát a példányra. Ez csak egy következmény.

Nem minden objektum

Az objektum orientált programozás alapötlete sokat segített abban, hogy jól karbantartható programokat lehessen készíteni. Az addigi legnagyobb gond, a felelőség határok kijelölése és betartása természetes módon oldódott meg.

Mi az alapötlet? Hogy a világban levő dolgok (autó/bank) bonyolultak lehetnek ugyan, de mégis többnyire egyszerűen tudjuk őket használni. Miért? Mert a bonyolultság el van rejtve, és mi csak egy egyszerűbb felületet látunk. Ez az egyszerűbb felület a programozásban az elérhető metódusokat jelenti. Van tehát egy felület, amit mi használunk, és van egy belső állapot, amit nem kell ismernünk. Elég általános elvnek tűnik igaz?

Van azonban egy másik, régóta használt, és elterjedt modellezési forma, ami mellesleg a programozásban is felbukkan, ez pedig a matematika. Egy m = n * 2 esetében nehéz erőltetni az objektum orientáltságot.

Halmazok és műveletek

Halmazokkal mindenki találkozott, aki járt középiskolába. Halmazok direkt vagy Descartes szorzatával szintén, és talán páran még emlékeznek, hogy ezek speciális tulajdonságú részhalmazai segítségével függvényeket/műveleteket, mindenféle gyönyörű konstrukciót lehet építeni. Hogy ezek hogyan is voltak, most nem érdekes, egy példán megnézzük miről is van szó.

Tegyük fel, hogy van egy halmaz, aminek elemei az időpontok 100 nanosecundumos bontásban 1 év január 1, 0 óra és a 10000-ik év között. Ennek a halmaznak irdatlan sok eleme van. Ezek az elemek jelképeznek valamit (egy pontot az időben), így egyfajta címkeként működnek. Mintha egy nagy kalapban lenne egy csomó címke.

A dátumokkal végezhetünk műveleteket. Egy adott dátumhoz hozzá lehet adni például egy napot. Hogyan illeszkedik egy művelet a kalapos képbe? Úgy, hogy ezek a műveletek nem mások, mint táblázatok, amelyek megmondják, hogy adott címkéhez, például 1992 január 2. 14:35:42.3457231-hez az “adj hozzá egy napot” művelet szerint 1992 január 3. 14:35:42.3457231 tartozik. Ha a kezemben van egy címke a kalapból, és a művelet táblázata, akkor tudom, hogy melyik másik címkét kell a kalapból elővenni, amikor “végrehajtom” a műveletet. Mindezt gyakran y = f(x) formában jelöljük a matematikában.

Megfoghatatlan elemek

Van egy gyenge pontja a halmazok kalapos példával történő bemutatásának. A szemléltetés kedvéért egy való világból merített példával próbáltam érzékeltetni hogyan működnek a halmazok és a halmazon végzett műveletek. Az általunk ismert fizikai világnak azonban nincsen meg egy lényeges tulajdonsága, ami a halmazoknak és elemeinek megvan, emiatt erre külön fel kell hívni a figyelmet.

Egyrészt, egy halmazból nem lehet kivenni vagy belerakni semmit, mint ahogyan egy kalappal ez megtehető. A halmaz a definíciója megalkotásától kezdve fix. De ez a kisebb dolog. A másik ami sántít, a címke, mint a halmaz egy eleme. Ha egy címkére gondolunk, egy papírfecni jut eszünkbe, amivel lehet körberohangálni. De ez így nem teljesen jó.

A példában nem a címke a lényeg, hanem a címke által hordozott információ. A 2004 Július 5, 6 óra 49 perc a fontos. Ez a halmaz eleme. Csak a 2004 Július 5, 6 óra 49 perc-et nehéz önmagában egy kalapba tenni. Kell hozzá valami hordozó, például egy cimke, amire feljegyezzük. És ezzel eljutottunk a halmazok elemeinek egy fontos tulajdonságához: akár hány címkére, vagy bármi másra írjuk fel, hogy 2004 Július 5, 6 óra 49 perc, ezek mind ugyanazt a halmazbeli elemet jelképezik, és még mindig a halmazbeli elem lesz a fontos, a címkék csak hordozók. Gyárthatunk ezer címkét, aztán összetéphetjük az összeset, sőt, át is firkálhatjuk egy másik dátumra, a 2004 Július 5, 6 óra 49 perc, mint a halmaz egy eleme mindettől független marad.

Objektumok vs halmazelemek

Miért volt fontos ennyi sort szentelni a halmazok elemeire? Azért, hogy érezhető legyen, a halmaz elemei teljesen más dolgok, mint a fizikai világ objektumaira hajazó programozásbeli objektumok. Egy objektumnak van egy állapota, ami időben változhat. A halmaz egy elemére ez értelmezhetetlen. Az objektumok esetében példányokról beszélhetünk, ahol minden példány saját állapottal rendelkezik. A halmazelemeknek nincsenek példányai, ilyesmiről értelmetlen beszélni. Az objektumot megtaláljuk valahol. A halmaz egy eleménél értelmetlen lehet a létezésről beszélni.

“Tisztán” objektum orientált nyelvek

Időnként – sajnos egyre gyakrabban – megesik az informatika történetében, hogy egy adott helyzetre hatékony eszközt/módszert elkezdenek hype-olni, és onnantól ciki lesz nem azt használni – bármilyen más helyzetben is. Ma például az eredetileg 10 soros lottószámgenerátor programot is a SOLID elveknek megfelelően kell megírni, persze TDD-vel. Így lesz a 10-ből tesztekkel együtt 2000 programsor, és a megíráshoz szükséges húsz percből három nap.

A 90-es években hasonló történt az objektum orientált programozással is, és ennek köszönhetően jelentek meg az olyan, egyébként általános célú nyelvek, amiben “csak” objektumok léteznek. Ezt a családot gazdagítja a C# is. A probléma ezzel az, hogy mint láttuk, az objektum orientált világ nem ad mindig jó modellt. Ezekbe a “tiszta” objektum orientált nyelvekbe azért valahogy visszacsorognak a nem objektum orientált konstrukciók, mivel szükség van rájuk. Ez a visszacsorgás azonban az eredetileg jónak gondolt, tiszta objektum orientált modell torzulását okozza, ami cserébe megzavarhatja a programozókat. Nézzük milyen gondokat okoz ez a C# esetében:

Object, mint mindennek őse

A C# esetében minden, a programozó által létrehozott konstrukció az Object-ből származik. Azzal nincsen gond, hogy minden, a C#-ban létrehozott konstrukción végrehajthatunk egy minimális számú műveletet – ez az objektum orientált megközelítésből jön, de az OO megközelítéstől elvonatkoztatva is van gyakorlati haszna. A gyakorlati haszon például, hogy minden konstrukcióról szerezhetünk egy szöveges leírást (ToString), vagy típusinformációkat (GetType).

A gond azzal van, hogy ennek az ősnek a neve Object. Miért? Mert ezzel az “ügyes” elnevezéssel a C# tervezői elérték, hogy szerencsétlen programozó csak objektumokban tudjon gondolkodni, pedig mint láttuk, ez nem mindig a helyes megközelítés.

ValueType a mutáció

Az Object egyik leszármazási ága a ValueType. Ez a leszármazási ág speciális tulajdonságokkal bír az objektum orientált világhoz képest. Az egyik legszembeötlőbb, hogy ezen az ágon kiiktatásra kerül az öröklődés – az objektum orientált programozás egyik alappillére nem működik. Ennek praktikus okai vannak, írtam róla gyakorlati megközelítésből az értéktípusokról szóló cikkben.

A másik szembeötlő tulajdonság, hogy ezen az ágon teljesen másképpen működnek a “példányra” történő hivatkozások, mint az Object-en és a többi Object-ből származó típus esetén. Miért? Mert a C# tervezői látszólag ráéreztek az esszenciális különbségekre az objektum orientált világ, és a halmazok világa között. A fizikai világban egy objektumot ilyen-olyan módon meg lehet mutatni. Leírjuk az objektum koordinátáit a térben, vagy megmondjuk az autó rendszámát, és ez alapján megkeressük a parkolóban. De tegyük meg ugyanezt a 2004 Július 5, 6 óra 49 perc-cel, vagy a Piros-sal vagy a 3.14-gyel…

Ha a való világban lemásolunk egy objektumra való hivatkozást, akkor az történik, hogy az objektum koordinátáját, vagy az autó rendszámát az egyik cetliről átírjuk a másikra. Innentől kezdve mind a két cetli ugyanarra az objektumra hivatkozik. A Piros, vagy a 3.14-esetén csak a teljes reprezentációt tudjuk átmásolni (felírjuk a másik cetlire is, hogy Piros, vagy 3.14). Mindez azért, mert fizikailag nem létezik Piros, meg 3.14, ezekre nem lehet az autóhoz hasonló módon hivatkozni.

A C#-ban az értéktípusok a Piros, a 3.14 és a 2004 Július 5, 6 óra 49 perc ábrázolására szolgálnak. Emiatt a hivatkozások másolása a teljes reprezentáció másolásával jár.

Az értéktípusoktól különböző leszármazási ágat a C#-ban referencia típusoknak nevezik. Valójában ezek az igazi objektum jellegű típusok (az értéktípusokkal szemben), így “Object Types” vagy hasonló név lenne a szerencsés. Ez a név azonban már furcsán hangzik azok után, hogy az ős típust szintén Object-nek hívják.

Nyelvi eszközök gondjai

Mostanra minden bizonnyal tiszta a különbség az értéktípusok és az objektum jellegű típusok (azaz referencia típusok) között. Az értéktípusok, mint például a DateTime egy halmaz elemeit képesek reprezentálni. A DateTime egy “példánya” csak egy “cetli”, ami az időpontok halmazának egy elemét reprezentálja. A nyelvi eszközök viszont, amivel a DateTime, vagy egyéb értéktípusokat leírhatjuk, ugyanazok, mint amivel az objektum jellegű konstrukciókat (a class-okat) leírhatjuk. Ez probléma forrása lehet, mivel egy halmazelem jellegű dolgot felruházhatunk állapottal, működéssel.

Tegyük fel, hogy van egy “A” típusú objektumom, és ennek egy “Művelet” nevű művelete. A programozásban az eddig cetlinek vagy címkéknek nevezett dolgok a változók. Van egy “a” változónk, ami az “A” típusú példányra hivatkozik. Bár implementációs részlet, tudjuk, hogy ez a hivatkozás – a referencia – egy memóriabeli “koordináta”. Amikor meghívjuk az “a” változón keresztül a “Művelet”-et, akkor az a referált objektumon hajtódik végre, annak az állapota fog a megfelelő módon változni, ha a művelet állapotváltozással jár. Maga az “a” változó, tehát a cetli érintetlen marad.

Ha “A” egy értéktípus, akkor teljesen más történik. Ebben az esetben az “a” változó az “A” által definiált halmaz egy elemét fogja reprezentálni a már korábban leírt módon. Mivel egy halmaz egy eleme nem jelenik meg fizikailag, a “Művelet” végrehajtása ezzel nem is tud mit kezdeni. Ha a “Művelet” ír valamit, akkor maximum a reprezentációt, azaz a cetlit tudja átírni. A SuperValue Increment() művelete kiradírozza a cetlire írt értéket, és eggyel nagyobbat ír vissza. Értéktípusoknál tehát a műveletek a cetlit (változót) módisíthatják, míg referencia típusoknál a cetli maga érintetlen maradt. Ugyanaz a szintaktika, más a működés.

Ez a különbség nem tűnik különösen veszélyesnek, és sok esetben nem is az. A kiinduló feladat viszont pont ezt a jelenséget mutatja be. Mit lehet tenni a problémák elkerülésére?

Immutable típusok

Egy Immutable példány jellemzője, hogy állapota nem módosítható a példány létrejötte után. Egyik fő előnyének azt szokták mondani, hogy ezek a példányok természetes módon thread-safe-ek. Egy másik előny lehet, hogy egy példány azonosítása olcsón megúszható az Interning Pattern használatával. Az C# értéktípusok esetében azonban egy újabb előny bukkan elő.

Értéktípusoknál nehéz példányról beszélni, így nehéz azt értelmezni, hogy a példány Immutable. Láttuk, hogy értéktípus esetében egy művelet a példány hiányában a változót (a cetlit) írja át. A művelet eredményeképpen a változó a halmaz egy másik elemét reprezentálja a továbbiakban. Az Immutable értéktípus esetén ez az átírás nem lehetséges, azaz egy végrehajtott művelet nem írhatja át a változó által reprezentált értéket (nem választhat más elemet a halmazból).

Ez persze nem jelenti azt, hogy egy értéktípusú változó Immutable megvalósítás esetén nem kaphat új értéket. Kaphat, direkt értékadással:

var startOfEvent = new DateTime(2000, 01, 01);
...
startOfEvent = new DateTime(2001, 01, 01);

Ezen felül műveleteket az Immutable típusokon is lehet végezni. Objektum jellegű Immutable típusoknál minden művelet egy új példányt hoz létre. Jól ismert példa erre a string:

var s1 = "Alma";       // ez egy string példány
var s2 = s1.ToUpper(); // s1 által referált példány változatlan marad,
                       // a ToUpper() új példányt gyárott.

Értéktípusok esetében nincs példány (legalábbis én nem szeretek példányként gondolni a cetlin lévő reprezentációra), az Immutable értéktípus egy művelete egy új ideiglenes változót (cetlit) gyárt le, amely a művelet eredményét reprezentálja.

var d1 = DateTime(2002, 01, 01);
var d2 = d1.AddDays(1); // Az AddDays() egy ideiglenes változót hoz létre, 
                        // amely 2002.01.02-t reprezentálja. Ez az ideiglenes 
                        // változó aztán átmásolódik d2-be.

Miért jobb ez? Azért, mert ebben az esetben direkt értékadással pontosan megmondjuk, hogy az értékváltozást melyik változón szeretnénk előidézni (most d2-őn). Ekkor nem fordul elő, hogy tévedésből egy ki tudja hanyadik ideiglenes másolat értékét írjuk át. Ha megtanultuk, hogy a DateTime szemantikája Immutable, akkor mindig alkalmazzuk a direkt értékadást, és ekkor a minifeladatnál szereplő helyzetben a ciklust eleve így írjuk le:

for (var i = 0; i < values.Length; i++)
{
  Values[i] = Values[i].AddDays(1); // Tudjuk, hogy ideiglenes változót hoz létre 
                                    // az új érték reprezentálásra, azt vissza
                                    // kell írni a megfelelő változóba.
} // for i

Ezek alapján a SuperValue helyes implementációja valami hasonló:

public struct SuperValue
{
    // Hogy biztos immutable legyen...
    private readonly int value;

    public SuperValue(int value)
    {
        this.value = value;
    } // SuperValue

    public int Value 
    { 
        get 
        { 
            return this.value; 
        } // get
    } // Vaule

    // A művelet egy új változó értékét állítja be, új "cetli" jön létre
    // az érték reprezentációjával
    public SuperValue Increment()
    {
        return new SuperValue(this.value + 1);
    } // Increment()
} // struct SuperValue

Egy helyesen felírt ciklus pedig:

for (var i = 0; i < values.Count; i++)
        
{
  values[i] = values[i].Increment();
        
} // for i

És ez most szép?

Saját véleményem szerint a fenti SuperValue megvalósítás még mindig furcsa. Ez abból adódik, hogy az objektum orientált világhoz kialakított szintaktikát használjuk. A values[i].Increment() még mindig olyan, mintha a values[i]-n, mint példányon hajtanánk végre műveletet. Matematikában az y = f(x) forma használatos. Ezek alapján a “régi” jelőlésmód lenne megfelelő, amit pedig ma már csak statikus metódusokkal tudunk némileg utánozni:

public static SuperValue Increment(SuperValue original)
{
    return new SuperValue(original.value + 1);
} // Increment()
...
values[i] = SuperValue.Increment(values[i]);

Másik oldalról, ebben az esetben nem lehet szépen egymásra fűzni a műveleteket, mint a String vagy a DateTime esetében:

d.AddDays(2).AddHours(4)

Konklúzió

Az objektum orientált programozási nyelvnek felépített C#-ban a halmaz/érték jellegű típusokat is az objektum orientáltságra kihegyezett eszközökkel lehet leírni. Ez bizonyos esetekben problémákhoz vezethet, emiatt értéktípusoknál nem helyes a lehetőségek teljes palettáját kihasználni. Célszerű az értéktípusokat Immutable módon felépíteni, és a módosításokat explicit értékadásokkal kifejezni.

  1. #1 by rlaci on 2012. March 11. - 15:46

    Nagyon szeretem ezt a blogot, minden blogbejegyzés egy kincs.🙂

    Tapasztalataim szerint jellemző, hogy “ha nem nagyon muszáj, nem használunk struct-ot”, mert ha nem homogén a csoport tudása, akkor akik nem ismerik annyira, azok csak hibákat generálnak vele. Ennek eredménye, hogy minden ilyen objektum inkább class.

    _Elvileg_ struct-ot kellene használni kicsi, jellemzően adattárolásra használt objektumoknál.
    Véleményed szerint _gyakorlatban_ mikor használjunk struct-ot?

    • #2 by Tóth Viktor on 2012. March 12. - 00:02

      Először is, köszönöm a dícséretet. A kérdésre válaszolva:
      Ha a dologra, amit modellezni akarunk, könnyű úgy gondolni, mint egy halmaznak egy elemére, és nehéz úgy gondolni, mint egy fizikailag megjelenő valamire, akkor az jó jelölt értéktípusnak. Néhány példa:
      Telefonszám, Adóazonosító jel, Koordináta, Sakktábla állás, Vas/Szén/Króm/Nikkel ötvözet arány. Ezek mind olyan dolgok, amik csak elvontan léteznek, nem lehet őket zsebre rakni, nem lehet rájuk objektumként gondolni.
      A gyakorlatban persze figyelembe kell venni az implementációs vonatkozásokat. Egy sakktábla állás ábrázolása például elég nagy lehet. Ha ezt .NET-ben értéktípussal tesszük meg, akkor jelentős teljesítményveszteséggel lehet számolni, ha egy sakkállást túl sokszor kell metódusokon keresztül utaztatni. Emiatt igen, hozzá kell tenni, hogy az értéktípus lehetőleg legyen kicsi. Elvileg egy ember/markoló/akármi törzsadatára is lehetne halmazelemként gondolni. Azonban ezek reprezentációja legtöbbször akkora, hogy szintén kevéssé praktikus értéktípusként implementálni őket.
      Referencia típusként felépítve is lehet azonban értékként viselkedő implementációt készíteni. Ha egy referencia típus példánya immutable, akkor nincs értelme állapotról beszélni, nincs értelme viselkedésről beszélni, ami az objektumok jellemzője. Az immutable referencia típust tehát már majdnem fel lehet fogni értéktípusként. Ez a trükkje a .NET string-nek is, vannak is olyanok, akik zavarba jönnek, ha megkérdezik tőlük, hogy a string érték vagy referencia típus. Ekkor persze figyelni kell az értéktípusok és a referencia típusok működésbeli különbségeire, például hiába immutable egy referencia típus, és hiába akarjuk értékként használni, az értéktípusokkal szemben az őket hivatkozó változót (lokális vagy member változót) mindig inicializálni kell – különben ott figyel egy null, ami pedig értéktípusoknál nem fordul elő.
      Tervezésnél azon is el kell gondolkodni, hogy a programban hogyan használjuk az adatot. Ha létrehozunk egy törzsadatot leíró konstrukciót, azt egyszer inicializáljuk, és így immutable módon adjuk körbe az alkalmazásban, akkor ez értéktípus jellegű (még akkor is, ha nem praktikus értéktípusként implementálni, így végül class lesz). Ugyanakkor ha a konstrukciót azért adogatjuk körbe, hogy abba bizonyos komponensek adatokat beleírjanak, akkor ez a körbeadogatott valami már inkább egy kitöltendő-bővítendő űrlaphoz hasonlít, ami viszont egyértelműen egy objektum – akár állapotokkal és műveletekkel.

  2. #3 by mjanoska on 2012. March 12. - 18:13

    Tecc! Két dolgot tennék még hozzá a valódi szenáriókhoz:
    1. Szituáció: high perf szerver környezetben zavar a GC – mit csinálsz, hogy megszüntesd a nem periodikus pulzálását?
    2. Példa: régebben volt egy nagyszerű MS research projekt, azóta érdeklődés hiányában (és stratégia váltás miatt) kihalt sajnos : Oslo/Modelling Toolkit/Sql Modelling CTP …
    Az érdekessége egy meta nyelv volt, az M. Ennek parser-e teljes mértékben (értsd: minden pontján) kiterjeszthető volt. A nyelvtan az MGrammar. Előre gondolva az elosztott/szálkezelős szitukra az arcok majdnem a teljes parse/execute infrastrukturát value type alapon csinálták meg🙂. Tanulságos rev engineer-elni … http://www.microsoft.com/download/en/details.aspx?id=24113

    • #4 by Tóth Viktor on 2012. March 12. - 20:54

      Kössz! Az 1-es ponthoz, ha már azon gondolkodom, hogy a gc zavar, akkor azon gondolkodom, hogy a .NET alapinfrastruktúra zavar. Ez az a helyzet, amikor lehet, hogy az eszközválasztás rossz, és nem is .NET alapon kellene csinálni a szervert, vagy legalább bizonyos részeit.

      • #5 by mjanoska on 2012. March 13. - 11:16

        🙂 nem a .Net alapinfrastruktúra az, hanem a memóriamodell. Még szerencse, hogy nem jutottak értelmes tranzakcionális memória implementációkhoz…
        Haskell/Erlang jobb választás tiszta szerver oldalra, üzleti logikára. Valószínűleg eljutnak majd az F# korlátait látva ugyanoda amúgy, tehát lassan de biztosan visszahat majd a funkcionális igény a teljes framework-re. Végül is a tiszta generics is lényegileg a funkcionális elemek miatt került be (és a collection topic a mellékhatás). Amúgy mostanra már egész használható módjai vannak a GC-nek… Msdn…

  3. #6 by dombijd on 2012. March 20. - 12:06

    Szia,

    Ha a structot változatlanul meghagyod és a for ciklusban a következőt csinálod, akkor is helyes lesz a megoldás:

    a = values[i]; a.increment(); values[i] = a

    Úgy látom a te megoldásodnál is módosítani kell a for ciklust. Mit gondolsz melyik a megfelelő megoldás?

    • #7 by Tóth Viktor on 2012. March 20. - 13:11

      Szia,

      Igen, az a megoldás is működik, de itt kicsit másról volt szó. Itt a probléma nem is igazán a for ciklus körül van, az csak a probléma egy következménye. A probléma az, hogy értéktípusok esetén kiszámíthatatlanul jönnek létre másolatok, és nem biztos, hogy a fejlesztő ezeket tudja követni. Ha egy értéktípus egyik művelete meg tudja változtatni az “állapotát” (azaz onnantól egy másik értéket reprezentál), akkor a fejlesztő lehet, hogy egy olyan másolaton teszi ezt meg, amiről nem tud.
      Ha az értéktípus immutable, akkor a metódusai kénytelenek új “példányt” (reprezentációt) visszaadni. Sajnos a C# nyelvi elemei miatt ez a szintaktika is becsapós, mert pontosan úgy néz ki az értéktípus immutable metódusa, mint egy objektum művelete, közben másképpen viselkednek. DE ha a fejlesztők megtanulják, hogy “értéktípus legyen immutable”, akkor használat közben megszólal a kis csengő, amikor értéktípuson dolgoznak (pl DateTime), hogy a művelet egy új reprezentációt hoz létre, és mivel új reprezentációt hoz létre, ez értékül kell adniuk valaminek, ha nem akarják hogy elvesszen.
      A te megoldásodban ezt az immutabilitást megcsinálod kézzel (te hozod létre az új reprezentációt, nem a művelet), tehát kb utánzod azt, aminek egy jól tervezett típus esetében magától meg kellene történnie (immutable esetén magátol létrejön a nálad “a”-nak nevezett változó). Ha kénytelen valaki egy rosszul tervezett típussal dolgozni, akkor egyébként igen, pontosan azt kell csinálni, mint amit írtál.
      Összefoglalva a jó megoldás két részből áll: értéktípusokat c#-ban immutable-nek kell tervezni, másrészt a fejlesztőknek ezt meg kell tanulni (oktatásban vagy könyvekben erre ki kellene térni). Ennek egyenes következménye lenne a helyes használat, azaz mindenki tudná, hogy ahol értéktípus módisítás van, ott tuti egy egyenlőség jelnek is lennie kell, különben az érték elveszik.
      Az más kérdés, hogy már a Microsoft sem csinálta minden értéktípusát immutable-re.

      • #8 by dombijd on 2012. March 20. - 21:13

        Igen egyetértek az érveléseiddel.

  1. Minifeladatok III. - pro C# tology - devPortal
  2. Megoldás – Minifeladatok II. - pro C# tology - devPortal

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: