C# bejegyzései

.NET, C#

Üres Recent Projects lista VS2005 -ben

Egy jó ideje fennt van a gépemen a Visual Studio 2005, párszor már újra is telepítettem, de ez a bosszantó hibája sosem szünt meg: a Recent Projects lista mindig üres volt. Akármit csináltam vele, nem volt hajlandó megjeleníteni az előzőleg használt projecteket.

De most büszkén jelenthetem be: rájöttem a megoldásra! Vagyis már 1 éve közzétették, csak most találtam rá :)

Az a lényeg, hogy valamiért a VS2005 a Windows MRU beállításai használja, és ha a windówsban le van tiltva a legutóbb használt dokumentumok gyűjtése, akkor nagy ívben lesz*rja, hogy a saját menüjében mik vannak beállítva. 

Gyors megldás a problémára, ha az alábbi registry kulcsban:
"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer"
Módosítjuk alábbi DWORD értékét:
"NoRecentDocsHistory"
0-ra.

 

Forrás: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=108731

 

 

 



Unix Timestamp C#-ban

Hogyan csináljunk linux / unix timestamp ból .NET C# DateTime értéket?
Hogyan .net DateTime to unix / linux timestamp in C#?

(Kulcsmondatok a keresőknek :)

 

Most találtam a neten ezt a hasznos kis kódot:

using System;
namespace utils
{
      public class UnixTimestamp
      {
            protected static readonly DateTime unixTPStart =
                  TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
            public static long toUTP(DateTime dt)
            {
                  TimeSpan toNow = dt.Subtract(unixTPStart);
                  return (long)Math.Round(toNow.TotalSeconds);
            }
            public static DateTime fromUTP(long tp)
            {
                  return unixTPStart.Add(new TimeSpan(tp * 10000000));
            }
      }
}


Sebességre optimalizálás dinamikus kódgenerálással

"Hogyan írjunk olyan programot, mely olyan programot ír,
ami százszor gyorsabb, mint ha mi írtuk volna."

Bevezető

A következőkben bemutatom, hogyan lehet a .NETes programokban bizonyos esetekben akár 100szoros (nem elírás, százszoros) sebességnövekedést elérni. A módszer lényege, hogy egy specifikus feladat végrehajtásához a programunk futás közben generálja le a legoptimálisabb IL assembly kódot. Ehhez a System.Reflection.Emit namespace metódusait használjuk.

A dinamikus kódgenerálás alkalmazása

A reflection emit (általában így nevezik) technológia alkalmazható többek közt:

  • Scriptek végrehajtására a webböngészőben
    1. A böngésző betölti a HTML oldalt
    2. A böngésző kiolvassa a scriptet az oldalból és átadja a scriptértelmezőnek (script engine)
    3. A szkriptértelmező létrehoz egy dinamikus programot a memóriában
    4. A szkriptértelmező a reflection emit segítségével legenerálja a scriptnek megfelelő assembly kódot a dinamikus programba.

  • Reguláris kifejezések lefordítására (compile)
    1. A fordító talál egy új regexet a programban.
    2. A regexet a programozó lefordítandónak jelölte, ezért a fordító készít hozzá egy specifikus kereső osztályt.
    3. A fordító értelmezi a reguláris kifejezést készít egy, csak a megadott kifejezést végrehajtó MSIL kódot, melyet reflection emittel beletesz az osztályba.
    4. A fordító példányosítja az előbb elkészített osztályt és lefuttatja a benne lévő kódot. Így hajtja végre a regexet.
    5. Ha a fordító legközelebb ugyanezzel a regexxel találkozik a forráskódban, fogja a hozzá elkészített osztályt, példányosítja és végrehajtja. Ezzel nagyban gyorsítja a program futását.

  • Algoritmusok specializációjára, egyedi esetre való optimalizációjára Pl:
    • Kód készítése megadott XSL séma validálásához
    • Célprogram készítése megadott XSLT transzformáció végrehajtásához
    • Egy általános titkosító algoritmus optimalizálása konkrét kulcsra

  • Saját kód generálására futásidőben
    • Például scriptelhető programok létrehozásához

A példaprogram

A reflection emit bemutatásához egy egyszerű példát választottam az O’reilly: Programming C# című könyv 18. fejezetéből:

Írjunk programot, amely összeadja 1-től n-ig az egész számokat!
Jelen esetben az n legyen 20.

Mi sem egyszerűbb, gondolnánk, egy for ciklus az egész:

  // sum numbers with a loop
 
public int DoSum (int n)
  {
       int result = 0;
       for(int i = 1;i <=n;i++)
       {
            result += i;
       }
       return result;

  }

Igen ám, de gondoljunk csak bele, hogyan működik a for ciklus! Van egy ciklusváltozó, mely minden lefutáskor növekszik, és van egy feltétel, mely szintén minden iterációban kiértékelődik. Ez a feladatunk szempontjából teljesen felesleges művelet, csak a vezérlési szerkezet működéséhez szükséges.

Mennyivel gyorsabb lenne egy olyan program, mely tényleg csak azt teszi, amire szükségünk van? Jelen esetben visszaadja az 1+2+3+4+5+…+n értéket.

  // brute force by hand
 
public int DoSum2()
  {
      return 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11
      + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20;
  }

Ebben az esetben az algoritmusunk tényleg a lehető legoptimálisabb, semmi felesleges funkciót nem végez.

Sebességmérés

Tehát mennyivel is gyorsabb így a számítás?

Ennek kiderítéséhez készítsünk egy teszt osztályt, mely egymilliószor kiszámolja az első 20 szám összegét az első, majd a második módszerrel:

Forráskód letöltése

Az eredmény:

Loop: Sum of (20) = 210
The elapsed time in milliseconds is: 156,25
Brute Force: Sum of (20) = 210
The elapsed time in milliseconds is: 31,25

Mint látjuk, az „optimális” megoldás 5-ször gyorsabb volt a for ciklusnál.
Valójában ez az eredmény több okból sem felel meg a valóságnak.

Az egyik ok a Windows működésében keresendő: A Windows Timer felbontása alapbeállításban 7,8125 ms. Ez azt jelenti hogy minden timerrel mért érték 7,8125 msnek a többszöröse. Ennél fogva ilyen rövid időtartamokat egyszerűen nem tudunk pontosan mérni. Két féle megoldás létezik a problémára:

  1. A Boot.ini ben az OSt betöltő rekordhoz hozzáteszünk egy /TIMERES=10000 kapcsolót. Ezzel a timer felbontását 0,9766 msre állítjuk. Én igazából ezt nem próbáltam ki, mert nem tudom milyen hatással van a rendszer működésére. A mi esetünkben úgysincs szükség ilyen fokú pontosságra, elég ha az arányokat látjuk. Ha valaki kipróbálja, írjon egy kommentet a tapasztalataival :)
  2. Sokkal többször futtatjuk le a tesztet, így a pontatlanságból eredő hiba kisebb lesz.

 

A másik ok pedig az, hogy csaltam. Vagyis igazából a fordító csal, mikor lefordítja a forráskódot. A kész programot Reflectorral vagy ILDasmmal megvizsgálva észrevehetjük a turpisságot:

 

 .method public hidebysig instance int32 DoSum2() cil managed
  {
        .maxstack 1
        .locals init (
                  int32 num1)
        L_0000: nop
        L_0001: ldc.i4 210
        L_0006: stloc.0
        L_0007: br.s L_0009
        L_0009: ldloc.0
        L_000a: ret
 
}

A fordító kiértékeli az összeadást és programba csak a végeredményt írja bele.
Ezt kiküszöbölhetjük, ha a függvényt az alábbi módon írjuk meg:

 

  public int DoSum3()
  {
      int ret = 0;
      ret += 1; ret += 2; ret += 3; ret += 4; ret += 5;
      ret += 6; ret += 7; ret += 8; ret += 9; ret += 10;
      ret += 11; ret += 12; ret += 13; ret += 14; ret += 15;
      ret += 16; ret += 17; ret += 18; ret += 19; ret += 20;
      return ret;
  }
 

Javított sebességmérés

Összefoglalva tehát az eddigi ténykedéseinket futtassuk le a módosított tesztet 10milliószor, mind a 3 függvényünkre.

Forráskód letöltése

Eredmény:

Loop: Sum of (20) = 210
The elapsed time in milliseconds is: 1765,625
Brute Force: Sum of (20) = 210
The elapsed time in milliseconds is: 250
Real Brute Force: Sum of (20) = 210
The elapsed time in milliseconds is: 406,25

Így is még valamivel több, mint 4szer gyorsabb az „optimális” algoritmusunk.

Dinamikus metódusok

OK. Ezután a rövid kis bevezető után vizsgáljuk meg újra a feladatot!

Így 20nál még nem gond leírni a forrást, de mit csináljunk ha 2000ig szeretnénk összeadni a számokat? Na mit? Használjunk reflection emitet! Vagyis generáljuk le futásidőben, a paraméter függvényében az optimális kódot. De hogyan is tegyük ezt?

Dinamikus metódust generálni a .NET 2.0 előtt csak úgy lehetett, hogy készített az ember egy dinamikus assemblyt, abba egy modult, abba osztályt, és végül abba a metódust. Majd az egészet példányosítani kellett, végül meg lehetett hívni a metódust. Szóval elég macerás volt.

Szerencsére nekünk már jobb a helyzetünk, mivel a 2-es .NETben bevezették a DynamicMethod osztályt. Segítségével a fenti házépítő procedúra nélkül, egyből gyárthatunk dinamikus metódust.

Alapvetően kétféle dinamikus metódus létezik: az egyszerű és az objektumhoz csatolt. Az egyszerű dinamikus metódus úgy viselkedik, mintha modulszintű statikus metódus lenne. Az objektumhoz csatolt metódusnál pedig egy példányosított objektumhoz kapcsoljuk hozzá a generált metódust.

Dinamikus metódusok készítése

A példánkhoz egy egyszerű dinamikus metódust fogunk készíteni. A procedúra 5 lépésből áll:

  1. Deklarálunk egy delegate típust. Ennek segítségével fogjuk majd meghívni a dinamikus metódust.

    private delegate int SumItInvoker();

  2. Készítünk egy tömböt, mely a metódus paramétereinek típusát tartalmazza

    Type[] methodArgs = new Type[0];

  3. Készítünk egy DynamicMethod objektumot. Ez fogja azonosítani a készülő dinamikus metódusunkat. A konstruktor paraméteriben meg kell adni
    • a függvény nevét
    • a visszatérési érték típusát
    • a paraméterek típusait tartalmazó tömböt
    • egy modult vagy objektumot, amelyhez hozzárendelődik a metódus

DynamicMethod sumIt = new DynamicMethod(
       "SumIt",
       typeof(int),
       methodArgs,
       typeof(DynamicSum).Module);

    A név megadása igazából csak a debuggolást könnyíti meg, ezzel a névvel a későbbiekben úgysem lehet hivatkozni a metódusra.

  1. Legeneráljuk a dinamikus metódus törzsét. Elkérjük a DynamicMethodunk ILGeneratorát, majd ennek segítségével emittáljuk az optimalizált összeadófüggvényünket:

                  ILGenerator generator =
             sumIt.GetILGenerator();
           // Ezt a kódot a DoSum3 függvény ILkódja alapján
           // lehet elkészíteni.
           // A stackbe 0át tesz. A megadott érték eléréséig
           // i-t a stackbe teszi konstansként.
           // Összeadja a stack tetején lévő két értéket
           // Az összeg a stackben lesz.
           generator.Emit(OpCodes.Ldc_I4, 0);
           for (int i = 1; i <= theValue;i++)
           {
                generator.Emit(OpCodes.Ldc_I4, i);
                generator.Emit(OpCodes.Add);
           }
           // return the value
           generator.Emit(OpCodes.Ret);

  2. Készítünk egy példányt az 1es pontban deklarált delegateből és hozzárendeljük az elkészített dinamikus metódushoz annak CreateDelegate függvényének segítségével.

    SumItInvoker DoSum = null;
    DoSum = (SumItInvoker) sumIt.CreateDelegate(typeof(SumItInvoker));

    Ezt a lépést a példaprogram konstruktorában fogjuk végrehajtani.

Sebességmérés újra

Módosítsuk a teszt osztályt, hogy az újonnan készített dinamikus függvényünket is vizsgálja.
A számokat 2000ig adja össze, egymilliószor.
A teljes forráskód innen tölthető le. (Ez tartalmazza a dinamikus assemblyvel készített metódust is, ennek magyarázatára nem térek ki.)

Az eredmény:

Sum of (2000) = 2001000
Looping. Elapsed milliseconds: 10328,125 for 1000000 iterations
Sum of (2000) = 2001000
Dynamic Assembly. Elapsed milliseconds: 171,875 for 1000000 iterations
Sum of (2000) = 2001000
Dynamic Method. Elapsed milliseconds: 125 for 1000000 iterations

82szeres gyorsulás! Azt hiszem, az eredmény magáért beszél.

Összefoglalás

Láthattuk, hogy egy egyszerű összeadás esetében is az optimális, futásidőben generált kód akár 80szor gyorsabban fut, mint egy hagyományos ciklus. Nem állítom, hogy minden esetben kifizetődő a dinamikus metódusok használata. Vannak esetek, mikor a metódus generálási idejével együtt lassabban fog lefutni a dinamikus kód, mint a hagyományos.

De ha valaki olyan alkalmazást fejleszt, ahol fontos a sebesség, próbálja ki, mennyivel gyorsabb a dinamikus metódusok használata. Igen, kell hozzá ismerni az MSIL assemblyt, de nem muszáj 0ról megírnunk az assembly kódot. C#ban leírhatjuk az optimális megoldás részleteit, majd Reflectorral, vagy ILDasmmal megvizsgálhatjuk a kódot, így össze lehet építeni bonyolultabb algoritmusokat is assemblyben. Nem egyszerű, de megéri a fáradtságot.

Remélem tetszett a cikk, és ha nem is fogod használni a dinamikus metódusokat a közeljövőben, legalább tudod, hogy léteznek, és milyen hatékonyak :)

 

Felhasznált irodalom:
A cikkhez felhasznált forráskódok az O'Reilly: Programming C# 4th edition című könyv példaprogramjain alapulnak. (Chapter 18\LoopVsBruteforce és Chapter 18\ReflectionEmit)
További infók és példaprogramok találhatók a reflection emitről az MSDN oldalain.


Reflector, avagy világíts bele a fekete dobozba!

Gondolkoztál már azon, hogy hogy működhet például az ArrayList? Szerettél volna belekukkantani egy StreamReader életébe? Szeretnéd úgy optmalizálni a programodat, hogy tudod, mi folyik a háttérben?

Akkor itt a megoldás! Világíts bele a fekete dobozba! Használj .NET Reflectort!

A felhasználók véleményeiből:

  • "- Iszonyú hasznos kis util, amióta kipróbáltam, mindig ezt használom!"
  • "- Egyszerűen nem tudok szabadulni tőle! Ez használni kell!"
  • "- Egy szó: NÉLKÜLÖZHETETLEN"

Ha valaha is foglalkoztál .NETes fejlesztéssel, mindenképpen próbáld ki. Ingyenes és nagyszerű!

Ja, és hogy mire jó?
Végülis is csak egy class browser, explorer, analyzer és dokumentáció nézegető, amivel minden .NET assemblyt megnézhetsz, kereshetsz bennük, visszafejtheted és elemezheted őket C#, Visual Basic vagy IL nyelven. Szóval semmi különös :)

De ez még nem minden!
Számtalan hasznosabbnál hasznosabb kiegészítő tölthető le hozzá INNEN!
Többek között egy olyan is, amely integrálja a programot a VisualStudioba így ni:

Reflector Demo


Van még hozzá

  • Delphi és Managed C++ nyelvű megjelenítő
  • Közvetlenül fájlba exportáló, bármilyen Reflector által támogatott nyelven.
  • Eltérés kereső (diff) két assembly között
  • Vizuális típus megjelenítő
  • Form megjelítő
  • Hívási lánc megjelenítő gráfos formában
  • Kódgenerátor COM komponens használatához

és további sok-sok kiegészítő, melyek kipróbálása után már el sem tudjuk képzelni az életünket nélkülük :)


A Reflection és a Valóság vagy valami ilyesmi :)

Találtam egy tök jó kis összefoglalót a Reflectionos trükkökről, íme:

http://weblogs.asp.net/avnerk/archive/2006/12/12/crossing-the-line-reflection-and-reality.aspx


Feltételesen lefutó hibakereső függvények készítése

Programozás közben igen hamar belefutunk abba a problémába, hogy itt-ott jó lenne kiíratni egy változó értékét, hogy ellenőrizzük, hogyan fut az algoritmusunk. Gond egy szál se, írassuk ki! Igen ám, de miután végeztünk a fejlesztéssel, jó lenne ezeket a kiíratásokat valahogy eltüntetni a végleges verzióból. Mihez is kezdjünk?


1. megoldás:

Kommentezzük jól körül a feltételes részeket, majd a végleges verzióban magát a programrészletet is kommentezzük ki!

class Program
      {
            static void DebugWrite(object debugdata)
            {
                  Console.WriteLine("Debug vagyok, üssetek! A változó értéke: {0}\n", debugdata);
            }

            static void Main(string[] args)
            {
                Console.WriteLine("Elindultam. Számolok.");               
                  //bonyolult számolóalgoritmus
                  int i;
                  for ( i = 0; i < 10; i++ )
                  {  
                        /*DEBUG BEGIN*/
                        DebugWrite(i);
                        /*DEBUG END*/
                  }              
              Console.WriteLine("Végeztem. Az eredmény: "+i);
                  Console.ReadLine();
            }
      }


Előnyök: rövid, házifeladat szintű programoknál egyszerűen használható, nem igényel különösebb tudást.

Hátrányok: minél többször alkalmazzuk annál biztosabb, hogy a végén bennemarad egy pár felesleges kiíratás.


2. megoldás:

Használjuk az #if, #else, #endif előfeldolgozó direktívákat!

class Program
      {
 
#if DEBUG
            static void DebugWrite(object debugdata)
            {
                  Console.WriteLine("Debug vagyok, üssetek! A változó értéke: {0}\n", debugdata);
            }
#endif 
            static void Main(string[] args)
            {
                Console.WriteLine("Elindultam. Számolok.");               
                  //bonyolult számolóalgoritmus
                  int i;
                  for ( i = 0; i < 10; i++ )
                  {  
#if DEBUG
                        DebugWrite(i);
#endif                       
                  }              
              Console.WriteLine("Végeztem. Az eredmény: "+i);
                  Console.ReadLine();
            }
      }


Előnyök: Majdnem minden programozási nyelv támogatja ezeket. Pontosan szabályozni tudjuk mely programrészek kerüljenek bele a végleges verzióba.

Hátrányok: széttagolják és nehezen áttekinthetővé teszik a forráskódot. Minden egyes debugfüggvény-híváskor alkalmazni kell őket.

3. megoldás:

Használjunk Conditional attribútumot!

class Program
      {
            [Conditional("DEBUG")]
            static void DebugWrite(object debugdata)
            {
                  Console.WriteLine("Debug vagyok, üssetek! A változó értéke: {0}\n", debugdata);
            }

            static void Main(string[] args)
            {
                Console.WriteLine("Elindultam. Számolok.");               
                  //bonyolult számolóalgoritmus
                  int i;
                  for ( i = 0; i < 10; i++ )
                  {  
                        DebugWrite(i);
                  }              
               Console.WriteLine("Végeztem. Az eredmény: "+i);
                  Console.ReadLine();
            }
      }



Előnyök: Szép :) Csak a függvény definíciójakor kell alkalmazni, a függvényhívásoknál nem.
Hátrányok: A feltételes függvény mindenképpen belefordul a programba, csak sosem hívódik meg.

 

Mint látjuk a legszebb és legáttekinthetőbb megoldást a Conditional attribútum használata nyújtja, mely a System.Debug namespaceben van. Egyszerre több feltételt is vizsgálhatunk a következő formában: 

[Conditional("DEBUG"), Conditional("TRACE")]

Ekkor a feltételek vagy kapcsolatban állnak egymással, tehát elég az egyiknek teljesülnie.
Fontos tudni azonban az alkalmazásának a feltételeit:

  • A feltételes függvénynek nem lehet visszatérési értéke. Csak void lehet.
  • Classon és structon belül deklarálható, interfaceben nem.
  • Nem kaphat override módosítót. Ha egy virtual függvényt Conditional attribútummal látunk el, akkor annak minden override párja automatikusan Conditional lesz.
  • Interface-metódus implementációjakor nem használható.

Jelentős különbség Conditional, és az előfeldolgozó direktívák között, hogy míg az attribútum egy teljes függvényre vonatkozik, a direktívákkal utasítás szinten szabályozhatjuk a végrehajtást. A Conditional attribútummal ellátott függvények belefordulnak a végleges programba, de futtatáskor a memóriába már nem töltődnek be. A direktívákkal szabályzott programrészek nem fordulnak le, ha a megadott feltétel nem teljesül.

Természetesen a .NETben sok más kifinomult technika létezik még a hibakeresés elősegítésére. A fenti egyszerű példák csak a feltételes függvényhívások demonstrálására készültek.

Referencia:


Automatikus verziószám növelés Visual Studio 2005ben

A külföldi weblapokon sokan azt hiszik, hogy Visual studio 2005 nem képes automatikusan növelni a build numbert minden fordításkor. Mindenféle makrókat írnak rá, meg összevissza gányolnak.

Te is azt hitted?

Akkor ki kell, hogy ábrándítsalak. A Visual Studio 2005 egyszerű módon rávehető arra, hogy a programod minden fordításakor autoincrementelje a buildnumbert.

De mi is az a buildnumber és mire jó az, ha automatikusan növekszik?

Minden .net assemblynek van egy 4 tagú verziószáma, mely 4 darab szám . -tal elválasztva. Például: 1.1.2165.1574. Ennek felépítése a következő:

  • Főverzió
  • Alverzó
  • Build szám
  • Javítás

Pl: Mikor .NET -es dll-t használsz, akkor annak a verziószáma alapján dönti el a keretrendszer, hogy melyiket töltse be a sok ugyanolyan nevű dll közül.

Ha minden fordításkor automatikusan növekszik az utolsó 2 tag, akkor bármikor megtudhatod egy lefordított binárisról, hogy körülbelül mikor készítetted, és mennyire van lemaradva az aktuális forráskódtól. Meg még ki tudja mire jó :)

OK, hogyan vegyem rá a VSt hogy növelgesse nekem a buildet?

Egyszerűen:

  1. A Solution Explorerben a Properties mappából nyisd meg az AssemblyInfo.cs fájlt!
  2. Scrollozz az aljára, ott kell lennie egy olyan bejegyzésnek, hogy

    [assembly: AssemblyVersion("1.0.0.0")]

  3. Ezd írd át a következőre:

    [assembly: AssemblyVersion("1.0.*")]

  4. Mentsd el solutiont. Ezzel készen is vagy.

Ez tényleg egyszerű! És hogyan lehet kiíratni a verziószámot a futó programban?

A Reflection osztályt kell hozzá használni. Ennek segítségével a program futás közben tud információkat lekérdezni saját magáról.

Például, tegyük fel hogy a Form-od címsorában szeretnéd látni a verzószámot. Ehhez a Készíts egy Load eseményt, és az eseménykezelő pedig az alábbi legyen:

private void Form1_Load(object sender, EventArgs e)
{
     System.Reflection.AssemblyName a = System.Reflection.Assembly.GetExecutingAssembly().GetName();
     this.Text += " " + a.Version.ToString(4);
}

Továbbá külön-külön is elérheted a verziószám tagjait a megfelelő propertyk használatával.


balinto 2006