نظرات مطالب
PHP سریعتر از ASP.NET! افسانه یا واقعیت؟
نمی دونم چرا ولی توی عمل واسه استارت زدن دات نت خیلی دیر میجنبه . حتی بعد از کامپایل و پابلیش یه خورده تاخیر داره ولی یکم که باهاش کار کنی میفته رو دور و خوب میشه !
ولی پی اچ پی همون اول سریعه . شاید بخاط پیچیدگی فریم ورک هست . چرا که entity , linq هم در پرژه استفاده شده است . 
مطالب
YVD

یک GUI مختصر و مفید برای کتابخانه سورس باز "دریافت فایل از یوتیوب" درست کردم که از آدرس زیر قابل دریافت است:



دریافت ، 115 KB
پیشنیاز برای اجرا: دات نت فریم ورک سه و نیم (اگر از ویندوز 7 استفاده می‌کنید، به صورت پیش فرض نصب است).


مطالب
بررسی مباحث Streaming در ‎‎‎.NET - مقدمه

هدف بررسی کامل مباحث Streaming در دات نت فریمورک می‌باشد.

Stream چیست؟

دنباله‌ای از بایت‌ها که می‌توان آنها را از یک backing store (انبار پشتیبان) خواند یا در آن نوشت.

Backing Store 

یک رسانه ذخیره سازی از جمله Disk-Drive، Memory و Network Location می‌باشد که به عنوان منبع یا مقصدی برای خواندن و نوشتن بایت‌ها به صورت دنباله‌ای، می‌توان از آن استفاده کرد.


زمانی که قرار است داده ذخیره شده به صورت Stream مصرف شود، مزیت مقیاس پذیری را نیز خواهید داشت. لذا لازم نیست با مشکل محدودیت حافظه نیز درگیر شوید.

آشنایی با معماری Streaming در دات نت

Streaming در دات نت، توسط سه مفهوم: backing store، decorators و adapters در برگرفته شده است. 

کلاسی به نام Stream در دات نت، برای ارائه یکسری متد مشترک برای Reading، Writing و Positioning در نظر گرفته شده است که همچنین کلاس پایه Backing Store Streams و Decorator Streams نیز می‌باشد. 

اعضای کلاس Stream را می‌توان به شکل زیر گروه بندی کرد:


در نظر داشته باشید که Stream ها، دارای اشاره گری به مکان جاری تحت عنوان Pointer نیز می‌باشند. مقدار پیش فرض آن «صفر» می‌باشد و زمانی که شروع به خواندن از Stream کنید، این خواندن از مکانی شروع می‌شود که Pointer به آنجا اشاره می‌کند. به شکل زیر توجه کنید:

اگر قرار باشد 3 بایت اول خوانده شود، لذا حالت زیر را خواهیم داشت: 

همانطور که مشخص است، Pointer مربوط به Stream به اولین خانه‌ای اشاره می‌کند که در Read‌های بعدی قرار است خوانده شود. در نهایت با خواندن دو بایت دیگر، حالت زیر را خواهیم داشت:

برای Reading و Writing متدهای زیر در کلاس System.IO.Stream در نظر گرفته شده‌اند:

(Read(byte[] buffer,int offset,int count

buffer: آرایه‌ای از بایت‌ها برای نگهداری داده‌ی خوانده شده از Stream
offset: برخلاف تصور، اندیسی است که مکان شروع ذخیره سازی در buffer را مشخص می‌کند و نه مکان شروع خواندن از Stream
count: بیشترین تعداد بایت برای خواندن از Stream می‌باشد. با توجه به اینکه ممکن است به انتهای Stream رسیده باشیم یا اینکه در شرایطی مثلا در Network Streamها چه بسا خود Stream تصمیم بگیرد تعداد بایت کمتری از این مقدار Count را برای ما ارائه دهد. از این رو همیشه مقداری که برای Count مشخص می‌کنید همان مقداری نیست که متد Read برای شما برگشت خواهد داد.
return: تعداد بایت‌هایی که خوانده شده است یا اگر به انتهای Stream رسیده باشیم «0» برگشت خواهد داد. از این رو تکه کد زیر برای خواندن کل داده به یکباره، قابل اطمینان نخواهد بود. 

byte[] dataToRead=new byte[stream.Length];
int bytesRead=stream.Read(dataToRead,0,dataToRead.Length);

راه حل جایگزین می‌تواند به شکل زیر باشد:

static byte[] ReadBytes(Stream stream)
    {
        // dataToRead will hold the data read from the stream
        byte[] dataToRead = new byte[stream.Length];
        //this is the total number of bytes read. this will be incremented 
        //and eventually will equal the bytes size held by the stream
        int totalBytesRead = 0;
        //this is the number of bytes read in each iteration (i.e. chunk size)
        int chunkBytesRead = 1;
        while (totalBytesRead < dataToRead.Length && chunkBytesRead > 0)
        {
            chunkBytesRead = stream.Read(dataToRead, totalBytesRead, 
                dataToRead.Length - totalBytesRead);
            totalBytesRead = totalBytesRead + chunkBytesRead;
        }
        return dataToRead;
    }
در کد بالا تا زمانیکه مجموع تعداد بایت‌های خوانده شده کوچکتر از طول Stream باشد و همچنین به انتهای Stream نرسیده باشیم (chunkBytesRead>0)، عملیات خواندن انجام خواهد گرفت. خوشبختانه در کلاس BinaryReader متدی برای این کار در نظر گرفته شده است که در آینده با آنها بیشتر آشنا خواهیم شد.
byte[] data = new BinaryReader (s).ReadBytes (1000);
ReadByte

return: یک بایت را از مکان فعلی که Pointer به آن اشاره می‌کند، می‌خواند. اگر خروجی «-1» باشد، به انتهای Stream رسیده اید.
برخلاف انتظار، خروجی این متد از نوع int می‌باشد؛ چرا که لازم است «-1» را نیز در برگیرد.

CanRead
ممکن است یک Stream از عملیات خواندن پشتیبانی نکند؛ این محدودیت از طریق حالت جاری Backing Store تعیین می‌شود. برای مثال:

با توجه به حالت FileStream که فقط برای Append کردن وهله سازی شده است، امکان خواندن را نخواهید داشت. بنابراین زمانیکه از کلاس شخص ثالثی برای خواندن از Stream استفاده می‌کنید، به‌صلاح است (به منظور Defensive Programming) که از متد CanRead قبل خواندن بهره ببرید.

(Write(byte[] array,int offset,int count

array: آرایه ای از بایت‌ها که قرار است در Stream درج شوند.
offset: اندیس شروع array برای درج کردن در Stream را مشخص می‌کند.
count: بیشترین تعداد بایتی که از array در Stream درج خواهد شد.

WriteByte

برای درج یک بایت در Stream استفاده می‌شود.

CanWrite

برای تشخص پشتیبانی کردن Stream از عملیات درج کردن مورد استفاده قرار خواهد گرفت.

عملیات Seeking 

با انجام هر یک از عملیات Read  و Write برروی Stream، باعث تغییر مکان Pointer مربوط به آن خواهید شد. در صورتیکه نیاز است به صورت انتخابی مکان خاصی از Stream را برای شروع درج کردن یا خواندن انتخاب کنید، Seeking کمک کننده خواهد بود.

باید توجه داشت که پشتیبانی از این عملیات به backing store مورد استفاده وابسته می‌باشد. از این رو باید دانست که MemoryStream و FileStream از Seeking پشتیبانی کرده ولی در مقابل NetworkStream، PipeStream و همچنین Decorator Streams به غیر از BufferedStream قابلیت Seeking را ندارند. BufferedStream با ایجاد پوششی برروی یک Stream به اصطلاح non-seekable، امکان Seeking درون Buffer داخلی خود را مهیا خواهد کرد.

برای عملیات Seeking نیز اعضایی در کلاس پایه System.IO.Stream در نظر گرفته شده است:

(Seek(long Offset,SeekOrigin origin

برای تنظیم مکان Pointer در Stream استفاده خواهد شد. 

(SetLength(long value

متدی برای تنظیم طول Stream، که اگر value ارسال شده کوچکتر از طول فعلی Stream باشد، آن را کوتاه کرده و در غیر این صورت، Stream موردنظر گسترش خواهد یافت. برای استفاده از این متد، Stream مورد نظر باید قابلیت Writing و Seeking را داشته باشد.

Length

پراپرتی فقط خواندنی که طول Stream را مشخص می‌کند. در صورتیکه Stream مورد نظر Seekable باشد، می‌توان از این پراپرتی بهر برد؛ این بدین معنی است که اگر با یک Stream از نوع non-seekable کار می‌کنید، در صورت استفاده از این خصوصیت، تمام بایت‌های Stream خوانده شده و بعد از قرار گرفتن در  یک buffer (به عنوان مثال در memory)، محاسبه خواهد شد.

Position

پراپرتی برای خواندن یا تنظیم مکان فعلی Pointer مربوط به Stream، می‌باشد. برای استفاده از آن لازم است Stream مورد استفاده Seekable باشد.

CanSeek

مشخص می‌کند که Stream مورد استفاده Seekable  می باشد یا خیر.

تفاوت متد Seek و پراپرتی Position برای عملیات Seeking

به طور خلاصه با استفاده از متد Seek انعطاف پذیری بالایی خواهید داشت. با مقدار دهی پراپرتی Position، این مقدار همیشه نسبت به ابتدای Stream در نظر گرفته خواهد شد (شکل زیر)؛ این در حالی است که با استفاده از متد Seek می‌توان مشخص کرد که مقدار Offset تنظیم شده نسبت به ابتدا، مکان جاری و یا انتهای Stream می‌باشد.

مثال:

        using (FileStream fs = File.Create(@"C:\files\testfile3.txt"))
        {
            // position is 0
            long pos = fs.Position;
            // sets the position to 1
            fs.Position = 1; 
         
            byte[] arrbytes = { 100, 101 };
            //writes the content of arrbytes into current position - which is 1
            fs.Write(arrbytes, 0, arrbytes.Length);
            //position is now 3 as its advanced by write
            pos = fs.Position;
            fs.Position = 0;
            byte[] readdata1 = ReadBytes(fs);
        }
در تکه کد بالا قصد داریم تعدادی بایت را در یک فایل متنی ذخیره کنیم. برای نشان دادن عملیات Seeking، ابتدا Position را با عداد «1» تنظیم کرده‌ایم. با استفاده از متد Write عمل درج بایت‌ها با شروع از مکان اندیس «1» را انجام داده‌ایم. در این لحظه، Position عدد «3» را نشان می‌دهد. حال برای خواندن Stream لازم است Position را با «0» مقدار دهی کنیم تا Pointer دوباره به ابتدای Stream اشاره کند و عملیات خواندن را انجام داده‌ایم. اگر تکه کد بالا را دیباگ کنیم، به نتیجه نشان داده شده در شکل زیر خواهیم رسید:

Closing and Flushing 

کلاس پایه System.IO.Stream اینترفیس IDisposable را پیاده سازی کرده است؛ لذا بهتر است برای آزاد سازی منابع از جمله: file handle در FileStream یا socket handle در NetworkStream، بعد از استفاده، متد Dispose آنها را فراخوانی کنید یا با وهله سازی آنها در بدنه using، این فراخوانی به صورت ضمنی انجام شود. 

نکته: باید توجه کنید که با Close (معادل Dispose) شدن decorator streamها ، backing store stream داخلی آنها نیز Close خواهد شد.

با توجه به اینکه I/O عملیات پرهزینه‌ای می‌باشد، برخی از انواع Stream‌ها به منظور بهبود کارآیی از یک مکانیزم بافر داخلی استفاده می‌کنند. به این شکل که عملیات Write، داده را به جای آنکه درون backing store ذخیره سازی کند، درون این بافر ذخیره سازی خواهد کرد. زمانیکه این بافر پر شود یا به صورت صریح متدهای Flush یا Close فراخوانی شده باشند، داده موجود در بافر درون backing store ذخیره خواهد شد. در نتیجه عملیات Read هم می‌تواند به بخشی از داده اصلی که هم اکنون درون بافر می‌باشد، دسترسی سریع‌تری داشته باشد. به عنوان مثال FileStream از این مکانیزم داخلی برخوردار است. سایز پیش فرض این بافر ‏‏4KB (قابل تنظیم است) می‌باشد. برای سایر مواردی که این امکان برایشان وجود ندارد، می‌توان از BufferedStream برای Decorate کردن Stream مورد نظر خود استفاده کرد.

نکته: به صورت پیش فرض، Streamها thread-safe نیستند و امکان خواندن و نوشتن همزمان توسط چند thread برروی یک stream مشترک را نخواهید داشت. برای حل این موضوع، متد استاتیکی در کلاس Stream تحت عنوان Synchronized در نظر گرفته شده است که یک thread-safe wrapper را به برروی stream ورودی در نظر گرفته و آن را به عنوان خروجی برگشت خواهد داد. 

 [HostProtection(SecurityAction.LinkDemand, Synchronization = true)]
    public static Stream Synchronized(Stream stream)
    {
      if (stream == null)
        throw new ArgumentNullException("stream");
      if (stream is Stream.SyncStream)
        return stream;
      return (Stream) new Stream.SyncStream(stream);
    }
اشتراک‌ها
از ASP.NET Core 1.0 RC2 چه خبر؟

- حرکت به سمت استفاده از NET Standard. که در برگیرنده‌ی دات نت فریم ورک فعلی، دات نت Core و Xamarin می‌شود.
- بازنویسی کامل قسمت تزریق وابستگی‌های آن و حذف service locatorهای فعلی آن تا حد ممکن
- حذف DNX و جایگزینی آن با NET CLI.
- تغییر نحوه‌ی کار با Hosting APIs  
- بهبود کارآیی Kestrel (the web/app server)

از ASP.NET Core 1.0 RC2 چه خبر؟
نظرات مطالب
تاریخچه‌ی نگارش‌های مختلف دات نت فریم ورک
کار جالبی کردی جناب نصیری. توسعه دات نت فریم ورک بر خلاف دیگر محصولات مایکروسافت، به سرعت پیش میره. هنوز در حال یادگیری تکنولوژی هایی به کار رفته در نسخه 3 و 3.5 دات نت هستیم که نسخه 4 با این همه تغییرات عرضه میشه.

جناب نصیری اگر ممکنه در مورد PLINQ توضیحاتی در وبلاگتون بدین، ممنون میشم.
مطالب
NET Just-In-Time Optimization.
هدف از توابع خطی(Inline)
استفاده از توابع، مقداری بر زمان اجرای برنامه می‌افزاید؛ هرچند که این زمان بسیار کم و در حد میلی ثانیه است، اما باری را بر روی برنامه قرار می‌دهد و علت این تاخیر زمانی این است که در فراخوانی و اعلان توابع، کامپایلر یک کپی از تابع مورد نظر را در حافظه قرار می‌دهد و در فراخوانی تابع، به آدرس مذکور مراجعه می‌کند و در عین حال آدرس موقعیت توقف دستورات در تابع main را نیز ذخیره می‌کند تا پس از پایان تابع، به آدرس قبل برگردد و ادامه‌ی دستورات را اجرا کند. در نتیجه این آدرس دهی‌ها و نقل و انتقالات بین آنها بار زمانی را در برنامه ایجاد می‌کند که در صورت زیاد بودن توابع در برنامه و تعداد فراخوانی‌های لازم، زمان قابل توجهی خواهد شد.
یکی از تکنیک‌های بهینه که برای کاهش زمان اجرای برنامه توسط  کامپایلر‌ها استفاده  می‌شود استفاده از  توابع خطی (inline) است. این امکان در زبان C با عنوان توابع ماکرو(Macro function) و در ++C با عنوان توابع خطی (inline function) وجود دارد.
در واقع توابع خطی به  کامپایلر پیشنهاد می‌دهند، زمانی که سربار فراخوانی تابع بیشتر از سربار بدنه خود متد باشد، برای کاهش هزینه و زمان اجرای برنامه از تابع به صورت خطی استفاده کند و یک کپی از بنده‌ی تابع را در قسمتی که تابع ما فراخوانی شده است، قرار دهد که مورد آدرس دهی از میان خواهد رفت!

نمونه ای از پیاده سازی این تکنیک در زبان ++C :
inline type name(parameters) 
{
    ...
}

بررسی متدهای خطی در سی شارپ
به مثال زیر توجه کنید:
قسمت‌های getter و setter مربوط به پراپرتی‌ها سربار اضافی بر کلاس Vector می‌افزایند. این موضوع شاید آنچنان مسئله‌ی مهمی نباشد. ولی فرض کنید این پراپرتی‌ها به شکل زیر داخل حلقه‌ای طولانی قرار گیرند. اگر با استفاده از یک پروفایلر زمان اجرای برنامه را زیر نظر بگیرید، خواهید دید که بیش از 90 درصد آن صرف فراخوانی‌های متد‌های بخش‌های get , set پراپرتی‌ها است. برای این منظور باید مطمئن شویم که فراخوانی این متد‌ها، به صورت خطی صورت می‌گیرد!
 public class Vector
  {
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }

    // ...
  }
برای این منظور آزمایشی را انجام می‌دهیم. فرض کنید کلاسی را به شکل زیر داشته باشیم:
public class MyClass
  {
    public int A { get; set; }
    public int C;
  }
و برای استفاده از آن به شکل زیر عمل کنیم:
  static void Main()
{
    MyClass target = new MyClass();
    int a = target.A;
    Console.WriteLine("A = {0}", a);
    int c = target.C;
    Console.WriteLine("C = {0}", c);
  }
بعد از دیباگ برنامه و مشاهده‌ی کدهای ماشین مربوط به آن خواهیم دید که متد مربوط به getter پراپرتی A به صورت خطی فراخوانی نشده است:
            int a = target.A;
  0000003e  mov         ecx,edi
  00000040  cmp         dword ptr [ecx],ecx
  00000042  call        dword ptr ds:[05FA29A8h]
  00000048  mov         esi,eax
  0000004a  mov         dword ptr [esp+4],esi
             int c = target.C;
  00000098  mov         edi,dword ptr [edi+4]
    MyClass.get_A() looks like this:
  00000000  push        esi
  00000001  mov         esi,ecx
  00000003  cmp         dword ptr ds:[03B701DCh],0
  0000000a  je          00000011
  0000000c  call        76BA6BA7
  00000011  mov         eax,dword ptr [esi+0Ch]
  00000014  pop         esi
  00000015  ret

چه اتفاقی افتاده است؟
 کامپایلر سی شارپ در زمان کامپایل، کد‌های برنامه را به کد‌های IL تبدیل می‌کند و JITکامپایلر، این کد‌های IL را گرفته و  کد ساده‌ی ماشین را تولید می‌کند. لذا به دلیل اینکه JIT با معماری پردازنده آشنایی کافی دارد، مسئولیت تصمیم گیری اینکه کدام متد به صورت خطی فراخوانی شود برعهده‌ی آن است. در واقع این JIT است که تشخیص می‌دهد که آیا فراخونی متد به صورت خطی مناسب است یا نه و به صورت یک معاوضه کار بین خط لوله دستورالعمل‌ها و کش است.
اگر شما برنامه‌ی خود را با  (F5) و همگام با دیباگ اجرا کنید، تمام بهینه سازی‌های JIT که Inline Method هم یکی از آنهاست، از کار خواهند افتاد. برای مشاهده‌ی کد بهینه شده باید با بدون دیباگ (CTRL+F5) برنامه خود را اجرا کنید که در آن صورت مشاهده خواهید کرد، متد getter  مربوط به پراپرتی A به صورت خطی استفاده شده است.
  int a = target.A;  
00000024  mov         ebx,dword ptr [edi+0Ch]


JIT محدودیت هایی برای فراخونی به صورت خطی متد‌ها دارد :
  1.  متد هایی که حجم کد IL آنها بیشتر از 32 بایت است.
  2. متد‌های بازگشتی.
  3. متدهایی که با اتریبیوت MethodImpl علامتگذاری شدند و MethodImplOptions.NoInlining  اعمال شده بر آن
  4. متدهای virtual
  5. متدهایی که دارای کد مدیریت خطا هستند
  6. Methods that take a large value type as a parameter 
  7. Methods with complicated flowgraphs 
برای اینکه در سی شارپ به کامپایلر اعلام کنیم تا متد مورد نظر به صورت خطی مورد استفاده قرار گیرد، در دات نت 4.5 توسط اتریبیوت MethodImpl و  اعمال  MethodImplOptions.AggressiveInlining که یک نوع شمارشی است می‌توان این کار را انجام داد. مثال:
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
class Program
{
    const int _max = 10000000;
    static void Main()
    {
         // ... Compile the methods.
        Method1();
        Method2();  
        int sum = 0;
        var s1 = Stopwatch.StartNew();
        for (int i = 0; i < _max; i++)
           {
              sum += Method1();
           }
         s1.Stop();
         var s2 = Stopwatch.StartNew();
         for (int i = 0; i < _max; i++)
             {
                sum += Method2();
             }
         s2.Stop();
         Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) /
        _max).ToString("0.00 ns"));
        Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) /
      _max).ToString("0.00 ns"));
        Console.Read();
      }
      static int Method1()
      {
         // ... No inlining suggestion.
         return "one".Length + "two".Length + "three".Length +
              "four".Length + "five".Length + "six".Length +
              "seven".Length + "eight".Length + "nine".Length +
               "ten".Length;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static int Method2()
    {
        // ... Aggressive inlining.
        return "one".Length + "two".Length + "three".Length +
           "four".Length + "five".Length + "six".Length +
           "seven".Length + "eight".Length + "nine".Length +
            "ten".Length;
    }
}
Output
7.34 ns    No options
0.32 ns    MethodImplOptions.AggressiveInlining
در واقع با اعمال این اتریبیوت، محدودیت شماره یک مبنی بر محدودیت حجم کد IL مربوط به متد، در نظر گرفته نخواهد شد .

 مطالعه بیشتر:
مطالب دوره‌ها
کتابخانه‌ی FastReflection
در حین توسعه‌ی کتابخانه‌ی PdfReport نیاز به یک کتابخانه‌ی Reflection سریع با پشتیبانی از خواصی خصوصا تو در تو بود. حاصل مطلب « دسترسی سریع به مقادیر خواص توسط Reflection.Emit » تبدیل به کتابخانه‌ی FastReflection ذیل شد که هم اکنون در PdfReport مورد استفاده است:
FastReflection.zip

            // کار با یک لیست جنریک تو در تو
            var list = new List<User>();
            for (int i = 0; i < 100; i++)
            {
                list.Add(new User
                {
                    Id = i+1,
                    Name = "name "+i,
                    Address = new Address
                    {
                        Address1 = "Addr1- "+i,
                        Address2 = "Addr2- "+i
                    }
                });
            }
            foreach (var item in list)
            {
                var propertyValues = new DumpNestedProperties().DumpPropertyValues(item, dumpLevel: 2);
                foreach (var result in propertyValues)
                {
                    Console.WriteLine(result.PropertyName + " -> " + result.PropertyValue);
                }
                Console.WriteLine();
            }
متد DumpPropertyValues ، توسط روش‌های Reflection.Emit تا تعداد سطحی را که مشخص می‌کنید، از شیء ارسالی به آن استخراج می‌کند. مباحث caching و استفاده مجدد از کدهای پویای تولید شده، در آن لحاظ شده و همچنین dumpLevel آن، از stack overflow در حین کار با پروکسی‌های پویای Entity framework جلوگیری می‌کند.
نظرات اشتراک‌ها
مقایسه کارآیی ORMهای مطرح دات نت
ADO.NET شبیه به زبان اسمبلی دسترسی به اطلاعات در دات نت هست. این پایین‌ترین سطحی است که برای کار با بانک‌های اطلاعاتی در دنیای دات نت وجود دارد. تمام ORMهای موجود لایه‌ای هستند بر روی این سطح.
مطالب
SQL تولیدی در NHibernate از کدام متد صادر شده است؟

اگر مطلب "ذخیره سازی SQL تولیدی در NH3" را دنبال کرده باشید که یک مثال عملی از "NHibernate 3.0 و عدم وابستگی مستقیم به Log4Net" بود، خروجی حاصل از آن به صورت زیر است:
---+ 12/29/2010 05:35:59.75 +---
SQL ...

---+ 12/29/2010 05:35:59.75 +---
SQL ...
و پس از مدتی این فایل هیچ حسی را منتقل نمی‌کند! یک سری SQL که لاگ شده‌اند. مشخص نیست کدام متد در کدام کلاس و کدام فضای نام، سبب صدور این عبارت SQL ثبت شده‌، گردیده‌ است.
خوشبختانه در دات نت فریم ورک می‌توان با بررسی Stack trace ، رد کاملی را از فراخوان‌های متدها یافت:
StackTrace stackTrace = new StackTrace();
StackFrame stackFrame = stackTrace.GetFrame(1);
MethodBase methodBase = stackFrame.GetMethod();
Type callingType=methodBase.DeclaringType;
با بررسی StackFrame ها امکان یافتن نام متدها، فضاهای نام و غیره میسر است. مثلا یکی از کاربردهای مهم این روش، ثبت فراخوان‌های متدی است که استثنایی را ثبت کرده است.
بر این اساس سورس مثال قبل را جهت درج اطلاعات فراخوان‌های متدها تکمیل کرده‌ام، که از این آدرس قابل دریافت است.

اکنون اگر از این ماژول جدید استفاده کنیم، خروجی نمونه‌‌ی آن به صورت زیر خواهد بود:
---+ 01/02/2011 02:19:24.98 +---
-- Void ASP.feedback_aspx.ProcessRequest(System.Web.HttpContext) [File=App_Web_4nvdip40.5.cs, Line=0]
--- Void Prog.Web.UserCtrls.FeedbacksList.Page_Load(System.Object, System.EventArgs) [File=FeedbacksList.ascx.cs, Line=23]
---- Void Prog.Web.UserCtrls.FeedbacksList.BindTo() [File=FeedbacksList.ascx.cs, Line=43]
----- System.Collections.Generic.IList`1[Feedback] Prog.GetAllUserFeedbacks(Int32) [File=FeedbackWebRepository.cs, Line=66]

SELECT ...
@p0 = 3 [Type: Int32 (0)]
به این معنا که عبارت SQL ثبت شده، حاصل از پردازش صفحه‌ی feedback.aspx، سپس متد Page_Load آن که از یوزر کنترل FeedbacksList.ascx استفاده می‌کند، می‌باشد. در اینجا فراخوانی متد BindTo سبب فراخوانی متد GetAllUserFeedbacks در فایل FeedbackWebRepository.cs واقع در سطر 66 آن گردیده است.
اینطوری حداقل می‌توان دریافت که SQL تولیدی دقیقا به کجا بر می‌گردد و چه متدی سبب صدور آن شده است.

ملاحظات:
این ماژول تنها در صورت وجود فایل pdb معتبر کنار اسمبلی‌های شما این خروجی مفصل را تولید خواهد کرد. در غیر اینصورت از آن صرفنظر می‌کند. (برای مثال نام فایل سورس فراخوان، شماره‌ی سطر فراخوان، حتی محل قرارگیری آن فایل بر روی کامپیوتر شما در فایل‌های pdb ثبت می‌گردند؛ به همین جهت توصیه اکید حین ارائه‌ی نهایی برنامه، حذف این نوع فایل‌ها است)

مطالب
آزمون واحد در MVVM به کمک تزریق وابستگی
یکی از خوبی‌های استفاده از Presentation Pattern‌ها بالا بردن تست پذیری برنامه و در نتیجه نگهداری کد می‌باشد.
MVVM الگوی محبوب برنامه نویسان WPF و Silverlight می‌باشد.  به صرف استفاده از الگوی MVVM نمی‌توان اطمینان داشت که ViewModel کاملا تست پذیری داریم. به عنوان مثلا اگر در ViewModel خود مستقیما DialogBox کنیم یا ارجاعی از View دیگری داشته باشیم نوشتن آزمون‌های واحد تقریبا غیر ممکن می‌شود. قبلا درباره‌ی این مشکلات و راه حل آن مطلب در سایت منتشر شده است : 
در این مطلب قصد داریم سناریویی را بررسی کنیم که ViewModel از Background Worker جهت انجام عملیات مانند دریافت داده‌ها استفاده می‌کند.
Background Worker کمک می‌کند تا اعمال طولانی در یک Thread دیگر اجرا شود در نتیجه رابط کاربری Freeze نمی‌شود.
به این مثال ساده توجه کنید : 
    public class BackgroundWorkerViewModel : BaseViewModel
    {
        private List<string> _myData;

        public BackgroundWorkerViewModel()
        {
            LoadDataCommand = new RelayCommand(OnLoadData);
        }

        public RelayCommand LoadDataCommand { get; set; }

        public List<string> MyData
        {
            get { return _myData; }
            set
            {
                _myData = value;
                RaisePropertyChanged(() => MyData);
            }
        }

        public bool IsBusy { get; set; }

        private void OnLoadData()
        {
            var backgroundWorker = new BackgroundWorker();
            backgroundWorker.DoWork += (sender, e) =>
                             {
                                 MyData = new List<string> {"Test"};
                                 Thread.Sleep(1000);
                             };
            backgroundWorker.RunWorkerCompleted += (sender, e) => { IsBusy = false; };
            backgroundWorker.RunWorkerAsync();
        }
    }

در این ViewModel با اجرای دستور LoadDataCommand داده‌ها از یک منبع داده دریافت می‌شود. این عمل می‌تواند چند ثانیه طول بکشد ، در نتیجه برای قفل نشدن رابط کاربر این عمل را به کمک Background Worker به صورت Async در پشت صحنه انجام شده است.
آزمون واحد این ViewModel اینگونه خواهد بود : 
    [TestFixture]
    public class BackgroundWorkerViewModelTest
    {
        #region Setup/Teardown

        [SetUp]
        public void SetUp()
        {
            _backgroundWorkerViewModel = new BackgroundWorkerViewModel();
        }

        #endregion

        private BackgroundWorkerViewModel _backgroundWorkerViewModel;

        [Test]
        public void TestGetData()
        {
              
            _backgroundWorkerViewModel.LoadDataCommand.Execute(_backgroundWorkerViewModel);

            Assert.NotNull(_backgroundWorkerViewModel.MyData);
            Assert.IsNotEmpty(_backgroundWorkerViewModel.MyData);
        }
    }

با اجرای این آزمون واحد نتیجه با آن چیزی که در زمان اجرا رخ می‌دهد متفاوت است و با وجود صحیح بودن کدها آزمون واحد شکست می‌خورد.
چون Unit Test به صورت همزمان اجرا می‌شود و برای عملیات‌های پشت صحنه صبر نمی‌کند در نتیحه این آزمون واحد شکست می‌خورد.

آزمون واحد شکست خورده

یک راه حل تزریق BackgroundWorker به صورت وابستگی به ViewModel می‌باشد. همانطور که قبلا اشاره شده یکی از مزایای استفاده از تکنیک‌های تزریق وابستگی  سهولت Unit testing می‌باشد.
در نتیجه یک Interface عمومی و 2  پیاده سازی همزمان و غیر همزمان جهت استفاده در برنامه‌ی واقعی و آزمون واحد تهیه می‌کنیم : 
   public interface IWorker
    {
        void Run(DoWorkEventHandler doWork);
        void Run(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler onComplete);
    }
جهت استفاده در برنامه‌ی واقعی : 
    public class AsyncWorker : IWorker
    {
        public void Run(DoWorkEventHandler doWork)
        {
            Run(doWork, null);
        }

        public void Run(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler onComplete)
        {
            var backgroundWorker = new BackgroundWorker();
            backgroundWorker.DoWork += doWork;
            if (onComplete != null)
                backgroundWorker.RunWorkerCompleted += onComplete;
            backgroundWorker.RunWorkerAsync();
            

        }
    }
جهت اجرا در آزمون واحد : 
    public class SyncWorker : IWorker
    {
        #region IWorker Members

        public void Run(DoWorkEventHandler doWork)
        {
            Run(doWork, null);
        }

        public void Run(DoWorkEventHandler doWork, RunWorkerCompletedEventHandler onComplete)
        {
            Exception error = null;
            var doWorkEventArgs = new DoWorkEventArgs(null);
            try
            {
                doWork(this, doWorkEventArgs);
            }
            catch (Exception ex)
            {
                error = ex;
                throw;
            }
            finally
            {
                onComplete(this, new RunWorkerCompletedEventArgs(doWorkEventArgs.Result, error, doWorkEventArgs.Cancel));
            }
        }

        #endregion
    }
در نتیجه ViewModel اینگونه تغییر خواهد کرد :
    public class BackgroundWorkerViewModel : BaseViewModel
    {
        private readonly IWorker _worker;
        private List<string> _myData;

        public BackgroundWorkerViewModel(IWorker worker)
        {
            _worker = worker;
            LoadDataCommand = new RelayCommand(OnLoadData);
        }

        public RelayCommand LoadDataCommand { get; set; }

        public List<string> MyData
        {
            get { return _myData; }
            set
            {
                _myData = value;
                RaisePropertyChanged(() => MyData);
            }
        }

        public bool IsBusy { get; set; }

        private void OnLoadData()
        {
            IsBusy = true; // view is bound to IsBusy to show 'loading' message.

            _worker.Run(
                (sender, e) =>
                    {
                        MyData = new List<string> {"Test"};
                        Thread.Sleep(1000);
                    },
                (sender, e) => { IsBusy = false; });
        }
    }

کلاس مربوطه به آزمون واحد را مطابق با تغییرات ViewModel :
    [TestFixture]
    public class BackgroundWorkerViewModelTest
    {
        #region Setup/Teardown

        [SetUp]
        public void SetUp()
        {
            _backgroundWorkerViewModel = new BackgroundWorkerViewModel(new SyncWorker());
        }

        #endregion

        private BackgroundWorkerViewModel _backgroundWorkerViewModel;

        [Test]
        public void TestGetData()
        {
              
            _backgroundWorkerViewModel.LoadDataCommand.Execute(_backgroundWorkerViewModel);

            Assert.NotNull(_backgroundWorkerViewModel.MyData);
            Assert.IsNotEmpty(_backgroundWorkerViewModel.MyData);
        }
    }

اکنون اگر Unit Test را اجرا کنیم نتیجه اینگونه خواهد بود :