
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
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));
}
}
}
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 reflection emit (általában így nevezik) technológia alkalmazható többek közt:
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.
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:
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:
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;
}
Összefoglalva tehát az eddigi ténykedéseinket futtassuk le a módosított tesztet 10milliószor, mind a 3 függvényünkre.
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.
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.
A példánkhoz egy egyszerű dinamikus metódust fogunk készíteni. A procedúra 5 lépésből áll:
private delegate int SumItInvoker();
Type[] methodArgs = new Type[0];
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.
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);
SumItInvoker DoSum = null;
DoSum = (SumItInvoker) sumIt.CreateDelegate(typeof(SumItInvoker));
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:
82szeres gyorsulás! Azt hiszem, az eredmény magáért beszél.
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.
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:
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:
Van még hozzá
é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 :)
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
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:
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:
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ő:
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:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyVersion("1.0.*")]
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.
RSS
balinto 2006