مطالب
intern pool جدول نگهداری رشته‌ها در دات‌نت
کد زیر را در نظر بگیرید :
object text1 = "test";
object text2 = "test";

object num1 = 1;
object num2 = 1;

Console.WriteLine("text1 == text2 : " + (text1 == text2));
Console.WriteLine("num1 == num2 : " + (num1 == num2));

به نظر شما چه چیزی در خروجی نمایش داده میشود؟

هر چهار متغییر text1  و text2 و num1 و num2 از نوع object هستند. با اینکه مقدار text1 و text2 یکی و مقدار num1 و num2 هم یکی است، نتیجه text1==text2 برابر true است اما num1==num2 برابر false.

خطی که text2 تعریف شده است را تغییر میدهیم :
object text2 = "test".ToLower();

اینبار با این که باز مقدار text1 و text2 یکی و هر دو "test" است، اما نتیجه text1==text2 برابر false است. انتظار ما هم همین است. دو object ایجاد شده است و یکی نیستند. تنها در صورتی باید نتیجه == آنها true باشد که هر دو به یک object اشاره کنند.

اما چرا در کد اولی اینگونه نبود؟

دلیل این کار برمیگردد به رفتار دات‌نت نسبت به رشتههایی که به صورت صریح در برنامه تعریف میشوند. CLR یک جدول برای ذخیره رشتهها به نام intern pool برای برنامه میسازد. هر رشتهای تعریف میشود، اگر در intern pool رشتهای با همان مقدار وجود نداشته باشد، یک رشته جدید ایجاد و به جدول اضافه میشود، و اگر موجود باشد متغییر جدید فقط به آن اشاره میکند. در واقع اگر 100 جای برنامه حتی در کلاسهای مختلف، رشتههایی با مقادیر یکسان وجود داشته باشند، برای همه آنها یک نمونه وجود دارد.

بنابراین text1 و text2 در کد اولی واقعا یکی هستند و یک نمونه برای آنها ایجاد شده است.

البته چند نکته در اینجا هست :
اگر text1 و text2 به صورت string تعریف شوند، نتیجه text1==text2 در هر دو حالت فوق برابر true است. چون عملگر == در کلاس string یکبار دیگر overload شده است:
public sealed class String : ...
{
    ...
    public static bool operator ==(string a, string b)
    {
      return string.Equals(a, b);
    }
   ...
}

این که کدام یک از overload‌ها اجرا شوند (کلاس پایه، کلاس اصلی، ...) به نوع دو متغییر اطراف == بستگی دارد. مثلا در کد زیر :
string text1 = "test";
string text2 = "test".ToLower();

Console.WriteLine("text1 == text2 (string) : " + (text1 == text2));
Console.WriteLine("text1 == text2 (object) : " + ((object)text1 == (object)text2));

اولین نتیجه true و دومی false است. چون در اولی عملگر == تعریف شده در کلاس string مورد استفاده قرار می‏‏گیرد اما در دومی عملگر == تعریف شده در کلاس object.

اگر دقت نشود این رفتار مشکلزا میشود. مثلا حالتی را در نظر بگیرید که text1 ورودی کاربر است و text2 از بانک اطلاعاتی خوانده شده است و با اینکه مقادیر یکسان دارند نتیجه == آنها false است. اگر تعریف عملگرها در کلاس object به صورت virtual بود و در کلاس‌های دیگر override می‌شد، این تغییر نوع‌ها تاثیری نداشت. اما عملگرها به صورت static تعریف می‌شوند و امکان override شدن ندارند. به همین خاطر کلاس object متدی به اسم Equals در اختیار گذاشته که کلاس‌ها آنرا override می‌کنند و معمولا از این متد برای سنجش برابری دو کلاس استفاده می‌شود :
object text1 = "test";
object text2 = "test".ToLower();

Console.WriteLine("text1 Equals text2 : " + text1.Equals(text2));
Console.WriteLine("text1 Equals text2 : " + object.Equals(text1, text2));

البته یادآور می‌شوم که فقط رشته‌هایی که به صورت صریح در برنامه تعریف شده‌اند، در intern pool قرار می‌گیرند و این فهرست شامل رشته‌هایی که از فایل یا بانک اطلاعاتی خوانده می‌شوند یا در برنامه تولید می‌شوند، نیست. این کار منطقی است وگرنه حافظه زیادی مصرف خواهد شد.

با استفاده از متد string.Intern می‌توان یک رشته را که در intern pool وجود ندارد، به فهرست آن افزود. اگر رشته در intern pool وجود داشته باشد، reference آنرا بر می‌گرداند در غیر اینصورت یک reference به رشته جدید به intern pool اضافه می‌کند و آنرا برمی‌گرداند.

یک مورد استفاده آن هنگام lock روی رشته‌هاست. برای مثال در کد زیر DeviceId یک رشته است که از بانک اطلاعاتی خوانده می‌شود و باعث می‌شود که چند job همزمان به یک دستگاه وصل نشوند :
lock (job.DeviceId)
{
    job.Execute();
}

اگر یک job با DeviceId برابر COM1 در حال اجرا باشد، این lock جلوی اجرای همزمان job دیگری با همین DeviceId را نمی‌گیرد. زیرا هر چند مقدار DeviceId دو job یکی است ولی به یک نمونه اشاره نمی‌کنند.

می‌توان lock را اینگونه اصلاح کرد :
lock (string.Intern(job.DeviceId))
{
    job.Execute();
}

مطالب
بازسازی کد: استخراج متد (Extract method)
در این بازسازی، با قطعه کدی روبرو هستیم که می‌توانیم آن را به صورت یک متد جدید، گروه‌بندی کنیم. نام متد، توضیح دهنده عملیاتی است که توسط آن قطعه کد انجام می‌شود. 
این بازسازی یکی از پر استفاده‌ترین بازسازی‌های کد است. معمولا زمانیکه متدی بلند باشد یا تکه کدی نیاز به کامنتی برای توضیح درباره کاری که انجام می‌دهد داشته باشد، چنین بازسازی ای نیاز است. در صورتیکه نام متدها به طور مناسبی انتخاب شوند، ایجاد متدهای کوچک از دو جهت مفید است: 
  1. خواندن متن متدهای بالادستی را آسان‌تر می‌کند. در واقع نام متد عملا نقش کامنت را ایفا می‌کند. مراجعه کنید به Self documenting code  
  2.  احتمال اینکه متدها دوباره استفاده شوند را بالا می‌برد

الگوهای متفاوتی در مورد طول متدها وجود دارد. ولی قاعده کلی حاکم بر این مورد «ارتباط منطقی بدنه و نام متد» است. در صورت رعایت این قاعده، عموما متدهایی با اندازه مناسب به‌وجود می‌آیند. 
به‌طور مثال قطعه کد زیر را مشاهده کنید:  
public void PrintInfo(dynamic person)
{ 
       Console.WriteLine(person.Name); 
       Console.WriteLine(person.LastName); 
       Console.WriteLine(person.Job.Title); 
       Console.WriteLine(person.Job.Salary); 
} 
این قطعه کد را می‌توان به صورت زیر بازسازی کرد:
public void PrintInfo(dynamic person) 
{ 
       Console.WriteLine(person.Name); 
       Console.WriteLine(person.LastName); 
       PrintJob(person.Job); 
} 
public void PrintJob(dynamic job) 
{ 
       Console.WriteLine(job.Title); 
       Console.WriteLine(job.Salary); 
}


مراحل انجام این بازسازی کد 

بعد از تشخیص تکه کدی که نیاز به استخراج متد در آن وجود دارد، مراحل زیر را انجام دهید:
  1. متد جدیدی ایجاد کنید و نام آن را بر اساس کاری که قرار است تکه کد انتخاب شده انجام دهد، تعیین کنید (نام را بر اساس کاری که قرار است انجام دهد انتخاب نمایید؛ نه نحوه انجام دادن آن کار).
  2. تکه کد نیازمند استخراج را از بدنه متد قبلی به بدنه متد جدید کپی کنید.
  3. کد استخراج شده را برای پیدا کردن متغیرهای محلی به‌جا مانده از متد اصلی بررسی کنید. این متغیرها احتمالا متغیرهای محلی متد جدید و پارامترهای آن را تشکیل خواهند داد. 
  4. اگر متغیرهای محلی موجود در تکه کد استخراج شده فقط در آن استفاده شده‌اند، آن‌ها را به صورت متغیرهای محلی موقتی در متد جدید ایجاد کنید. 
  5. متغیرهای محلی تکه کد استخراج شده را بررسی کنید که آیا در جایی از آن ویرایش شده‌اند یا خیر؟ در صورتیکه ویرایش شده باشند، بررسی کنید که آیا می‌توان آن را به عنوان خروجی متد جدید در نظر گرفت و متغیرها را مقداردهی کرد یا خیر؟ (توضیح بیشتر در مثال) 
  6. پارامترهای متناظری را با متغیرهای محلی موقتی ایجاد شده، در متد جدید ایجاد کنید.
  7. بعد از این که تکلیف تمامی متغیرهای محلی مشخص شد، کد را کامپایل کنید.
  8. در متد مبدا تکه کد استخراج شده را به فراخوانی متد جدید تغییر دهید.
  9. کد را کامپایل و تست کنید .

مثال: تکه کدی با ویرایش کردن متغیرهای محلی 
ساده‌ترین حالتیکه کد استخراج شده از متغیرهای محلی متد مبدا استفاده می‌کند زمانی است که تنها مقادیر این متغیرها را می‌خواند. مثال ذکر شده در ابتدای نوشتار از این نوع است.
زمانی که متغیری در تکه کد استخراج شده ویرایش شده باشد، معمولا با دو حالت روبرو هستیم:
  • متغیر ویرایش شده فقط در تکه کد استخراج شده ویرایش و استفاده شده‌است.
  • متغیر ویرایش شده بعد از تکه کد استخراج شده نیز در بدنه متد اصلی مورد استفاده قرار گرفته‌است.
در حالت دوم نیاز است مقدار متغیر ویرایش شده، به متد بالادستی برگردانده شود. به طور مثال تکه کدی را در نظر بگیرید که مقدار رقم نهایی قابل پرداخت یک آیتم سفارش را در آن مقداردهی می‌کند. این روال را می‌توان به صورت زیر پیاده سازی کرد:
public void SetPayableAmount(dynamic orderItem) 
{ 
        var price = orderItem.Price; 
        var discount = 0; 
        var discountPlan = GetDiscountPlan(); 
        var discountPercentage = discountPlan.Percentage; 
        discount = price * discountPercentage; 
        orderItem.PayableAmount = orderItem.Price - discount 
} 
private dynamic GetDiscountPlan() 
{ 
        throw new NotImplementedException(); 
}
با کمی دقت در تکه کد بالا می‌توان به این نتیجه رسید که جدا کردن روال محاسبه تخفیف یک طراحی و بازسازی خوب است. در سه خطی که مربوط به محاسبه تخفیف است یک متغیر محلی به نام discount مقداردهی شده است و یک متغیر محلی به نام price فقط خوانده شده‌است. این کد را می‌توان به صورت زیر بازسازی کرد: 
public void SetPayableAmount(dynamic orderItem)
{ 
        var price = orderItem.Price; 
        var discount = GetDiscount(price); 
        orderItem.PayableAmount = orderItem.Price - discount; 
} 
public dynamic GetDiscount(decimal price) 
{ 
        var discountPlan = GetDiscountPlan(); 
        var discountPercentage = discountPlan.Percentage; 
        return price * discountPercentage; 
} 
private dynamic GetDiscountPlan() 
{ 
       throw new NotImplementedException(); 
}
در این بازسازی، به دلیل نیاز کد استخراج شده به مقدار متغیر price، این متغیر را به صورت یک پارامتر، به متد جدید ایجاد شده، ارسال کردیم. همچین مقدار تخفیف محاسبه شده را نیز به دلیل نیاز به آن در متد بالادستی به صورت مقدار خروجی به آن متد بازگرداندیم.

ابزارهای کمکی برای این بازسازی کد 

در ابزارهای زیر امکاناتی برای انجام این بازسازی کد تعبیه شده است:
مایکروسافت ویژوال استودیو: میانبر Ctrl+R,M را بعد از انتخاب کد منتخب برای استخراج، استفاده نمایید. 
Jetbrains Resharper: این ابزار را می‌توان با میانبرهای مایکروسافت ویژوال استودیو استفاده کرد. امکانات کلی این ابزار از امکانات موجود در مایکروسافت ویژوال استودیو بیشتر است.  
مطالب
نوشتن آزمون‌های واحد به کمک کتابخانه‌ی Moq - قسمت اول - معرفی
گاهی از اوقات، برای نوشتن آزمون‌های واحد، ایزوله سازی قسمتی که می‌خواهیم آن‌را بررسی کنیم، از سایر قسمت‌های سیستم مشکل می‌شود. برای مثال اگر در کلاسی کار اتصال به بانک اطلاعاتی صورت می‌گیرد و قصد داریم برای آن آزمون واحد بنویسیم، اما قرار نیست که الزاما با بانک اطلاعاتی کار کنیم، در این حالت نیاز به یک نمونه‌ی تقلیدی یا Mock از بانک اطلاعاتی را خواهیم داشت، تا کار دسترسی به بانک اطلاعاتی را شبیه سازی کند. در این سری با استفاده از کتابخانه‌ی بسیار معروف Moq (ماک‌یو تلفظ می‌شود؛ گاهی از اوقات هم ماک)، کار ایزوله سازی کلاس‌ها را انجام خواهیم داد، تا بتوانیم آن‌ها را مستقل از هم آزمایش کنیم.


Mocking چیست؟

فرض کنید برنامه‌ای را داریم که از تعدادی کلاس تشکیل شده‌است. در این بین می‌خواهیم تعدادی از آن‌ها را به صورت ایزوله‌ی از کل سیستم آزمایش کنیم. البته باید درنظر داشت که این کلاس‌ها در حین اجرای واقعی برنامه، از تعدادی وابستگی خاص در همان سیستم استفاده می‌کنند. برای مثال کلاسی در این بین برای بررسی میزان اعتبار مالی یک کاربر، نیاز دارد تا با یک وب سرویس خارجی کار کند. اما چون می‌خواهیم این کلاس را به صورت ایزوله‌ی از کل سیستم آزمایش کنیم، اینبار بجای استفاده‌ی از وابستگی واقعی این کلاس، آن وابستگی را با یک نمونه‌ی تقلیدی یا Mock object در اینجا، جایگزین می‌کنیم.
بنابراین Mocking به معنای جایگزین کردن یک وابستگی واقعی سیستم که در زمان اجرای آن مورد استفاده قرار می‌گیرد، با نمونه‌ی تقلیدی مختص زمان آزمایش برنامه، جهت بالابردن سهولت نوشتن آزمون‌های واحد است.


دلایل و مزایای استفاده‌ی از Mocking

- یکی از مهم‌ترین دلایل استفاده‌ی از Mocking، کاهش پیچیدگی تنظیمات اولیه‌ی نوشتن آزمون‌های واحد است. برای مثال اگر در برنامه‌ی خود از تزریق وابستگی‌ها استفاده می‌کنید و کلاسی دارای چندین وابستگی تزریق شده‌ی به آن است، برای آزمایش این کلاس نیاز به تدارک تمام این وابستگی‌ها را خواهید داشت تا بتوان این کلاس را وهله سازی کرد و همچنین برنامه را نیز کامپایل نمود. اما در این بین ممکن است آزمایش متدی در همان کلاس، الزاما از تمام وابستگی‌های تزریق شده‌ی در یک کلاس استفاده نکند. در این حالت، Mocking می‌تواند تنظیمات پیچیده‌ی وهله سازی این کلاس را به حداقل برساند.
- Mocking می‌تواند سبب افزایش سرعت اجرای آزمون‌های واحد نیز شود. برای مثال با تقلید سرویس‌های خارجی مورد استفاده‌ی در برنامه (هر عملی که از مرزهای سیستم رد شود مانند کار با شبکه، بانک اطلاعاتی، فایل سیستم و غیره)، می‌توان میزان I/O و همچنین زمان صرف شده‌ی به آن‌را به حداقل رساند.
- از mock objects می‌توان برای رهایی از مشکلات کار با مقادیر غیرمشخص استفاده کرد. برای مثال اگر در کدهای خود از DateTime.Now استفاده می‌کنید یا اعداد اتفاقی و امثال آن، هربار که آزمون‌های واحد را اجرا می‌کنیم، خروجی متفاوتی را دریافت کرده و بسیاری از آزمون‌های نوشته شده با مشکل مواجه می‌شوند. به کمک mocking می‌توان بجای این مقادیر غیرمشخص، یک مقدار ثابت و مشخص را بازگشت دهد.
- چون به سادگی می‌توان mock objects را تهیه کرد، می‌توان کار توسعه و آزمایش برنامه را پیش از به پایان رسیدن پیاده سازی اصلی سرویس‌های مدنظر، همینقدر که اینترفیس آن سرویس مشخص باشد، شروع کرد که می‌تواند برای کارهای تیمی بسیار مفید باشد.
- اگر وابستگی مورد استفاده ناپایدار و یا غیرقابل پیش بینی است، می‌توان توسط mocking به یک نمونه‌ی قابل پیش بینی و پایدار مخصوص آزمون‌های برنامه رسید.
- اگر وابستگی خارجی مورد استفاده به ازای هر بار استفاده، هزینه‌ای را شارژ می‌کند، می‌توان توسط mocking، هزینه‌ی آزمون‌های برنامه را کاهش داد.


Unit test چیست؟

بدیهی است در کنار آزمایش ایزوله‌ی قسمت‌های مختلف برنامه توسط mocking، باید کل برنامه را جهت بررسی دستیابی به نتایج واقعی نیز آزمایش کرد که به این نوع آزمون‌ها، آزمون یکپارچگی (Integration Tests)، API Tests ،UI Tests و غیره می‌گویند که در کنار Unit tests ما حضور خواهند داشت. بنابراین اکنون این سؤال مطرح می‌شود که یک Unit چیست؟
در برنامه‌ای که از چندین کلاس تشکیل می‌شود، به یک کلاس، یک Unit گفته می‌شود. همچنین اگر در این سیستم، دو یا چند کلاس با هم کار می‌کنند (کلاسی که از چندین وابستگی استفاده می‌کند)، این‌ها با هم نیز یک Unit را تشکیل دهند. بنابراین تعریف Unit بستگی به نحوه‌ی درک عملکرد یک سیستم و تعامل اجزای آن با هم دارد.


واژه‌های متناظر با Mock objects

در حین مطالعه‌ی منابع مرتبط با آزمون‌های واحد ممکن است با این واژه‌های تقریبا مشابه مواجه شوید: fakes ،stubs ،dummies و mocks. اما تفاوت آن‌ها در چیست؟
- Fakes در حقیقت یک نمونه پیاده سازی واقعی، اما غیرمناسب محیط واقعی و اصلی پروژه‌است. برای نمونه EF Core به همراه یک نمونه in-memory database هم هست که دقیقا با مفهوم Fakes تطابق دارد.
- از Dummies صرفا جهت تهیه‌ی پارامترهای مورد نیاز برای اجرای یک آزمایش استفاده می‌شوند. این پارامترها، هیچگاه در آزمایش‌های انجام شده مورد استفاده قرار نمی‌گیرند.
- از Stubs برای ارائه‌ی پاسخ‌هایی مشخص به فراخوان‌ها استفاده می‌شود. برای مثال یک متد یا خاصیت، دقیقا چه چیزی را باید بازگشت دهند.
- از Mocks برای بررسی تعامل اجزای مختلف در حال آزمایش استفاده می‌شود. آیا متدی یا خاصیتی مورد استفاده قرار گرفته‌است یا خیر؟

باید درنظر داشت که زمانیکه یک شیء Mock را توسط کتابخانه‌ی Moq تهیه می‌کنیم، هر سه مفهوم stubs ،dummies و mocks را با هم به همراه دارد. به همین جهت در این سری زمانیکه به یک mock object اشاره می‌شود، هر سه مفهوم مدنظر هستند.

واژه‌ی دیگری که ممکن است در این گروه زیاد مشاهده شود، «Test double» نام دارد که ترکیب هر 4 مورد fakes ،stubs ،dummies و mocks می‌باشد. در کل هر زمانیکه یک شیء مورد استفاده‌ی در زمان اجرای برنامه را جهت آزمایش ساده‌تر آن جایگزین می‌کنید، یک Test double را ایجاد کرده‌اید.


بررسی ساختار برنامه‌ای که می‌خواهیم آن‌را آزمایش کنیم

در این سری قصد داریم یک برنامه‌ی وام دهی را آزمایش کنیم که قسمت‌های مختلف آن دارای وابستگی‌های خاصی می‌باشند. ساختار این برنامه را در ادامه مشاهده می‌کنید:


موجودیت‌های برنامه‌ی وام دهی
namespace Loans.Entities
{
    public class Applicant
    {
        public int Id { set; get; }

        public string Name { set; get; }

        public int Age { set; get; }

        public string Address { set; get; }

        public decimal Salary { set; get; }
    }
}

namespace Loans.Entities
{
    public class LoanProduct
    {
        public int Id { set; get; }

        public string ProductName { set; get; }

        public decimal InterestRate { set; get; }
    }
}

namespace Loans.Entities
{
    public class LoanApplication
    {
        public int Id { set; get; }

        public LoanProduct Product { set; get; }

        public LoanAmount Amount { set; get; }

        public Applicant Applicant { set; get; }

        public bool IsAccepted { set; get; }
    }

    public class LoanAmount
    {
        public string CurrencyCode { get; set; }

        public decimal Principal { get; set; }
    }
}

مدل‌های برنامه‌ی وام دهی

namespace Loans.Models
{
    public class IdentityVerificationStatus
    {
        public bool Passed { get; set; }
    }
}

سرویس‌های برنامه‌ی وام دهی

using Loans.Models;

namespace Loans.Services.Contracts
{
    public interface IIdentityVerifier
    {
        void Initialize();

        bool Validate(string applicantName, int applicantAge, string applicantAddress);

        void Validate(string applicantName, int applicantAge, string applicantAddress, out bool isValid);

        void Validate(string applicantName, int applicantAge, string applicantAddress,
            ref IdentityVerificationStatus status);
    }
}

namespace Loans.Services.Contracts
{
    public interface ICreditScorer
    {
        int Score { get; }

        void CalculateScore(string applicantName, string applicantAddress);
    }
}

using System;
using Loans.Entities;
using Loans.Services.Contracts;

namespace Loans.Services
{
    public class LoanApplicationProcessor
    {
        private const decimal MinimumSalary = 1_500_000_0;
        private const int MinimumAge = 18;
        private const int MinimumCreditScore = 100_000;

        private readonly IIdentityVerifier _identityVerifier;
        private readonly ICreditScorer _creditScorer;

        public LoanApplicationProcessor(
            IIdentityVerifier identityVerifier,
            ICreditScorer creditScorer)
        {
            _identityVerifier = identityVerifier ?? throw new ArgumentNullException(nameof(identityVerifier));
            _creditScorer = creditScorer ?? throw new ArgumentNullException(nameof(creditScorer));
        }

        public bool Process(LoanApplication application)
        {
            application.IsAccepted = false;

            if (application.Applicant.Salary < MinimumSalary)
            {
                return application.IsAccepted;
            }

            if (application.Applicant.Age < MinimumAge)
            {
                return application.IsAccepted;
            }

            _identityVerifier.Initialize();

            var isValidIdentity = _identityVerifier.Validate(
                application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);

            if (!isValidIdentity)
            {
                return application.IsAccepted;
            }

            _creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address);
            if (_creditScorer.Score < MinimumCreditScore)
            {
                return application.IsAccepted;
            }

            application.IsAccepted = true;
            return application.IsAccepted;
        }
    }
}

using System;
using Loans.Models;
using Loans.Services.Contracts;

namespace Loans.Services
{
    public class IdentityVerifierServiceGateway : IIdentityVerifier
    {
        public DateTime LastCheckTime { get; private set; }

        public void Initialize()
        {
            // Initialize connection to external service
        }

        public bool Validate(string applicantName, int applicantAge, string applicantAddress)
        {
            Connect();
            var isValidIdentity = CallService(applicantName, applicantAge, applicantAddress);
            LastCheckTime = DateTime.Now;
            Disconnect();

            return isValidIdentity;
        }

        private void Connect()
        {
            // Open connection to external service
        }

        private bool CallService(string applicantName, int applicantAge, string applicantAddress)
        {
            // Make call to external service, interpret the response, and return result

            return false; // Simulate result for demo purposes
        }

        private void Disconnect()
        {
            // Close connection to external service
        }

        public void Validate(string applicantName, int applicantAge, string applicantAddress, out bool isValid)
        {
            throw new NotImplementedException();
        }

        public void Validate(string applicantName, int applicantAge, string applicantAddress,
            ref IdentityVerificationStatus status)
        {
            throw new NotImplementedException();
        }
    }
}
توضیحات:
هدف از این برنامه، درخواست یک وام جدید است. Application در اینجا به معنای درخواست یا فرم جدید است و Applicant نیز شخصی است که این درخواست را داده‌است.
در اینجا بیشتر تمرکز ما بر روی کلاس LoanApplicationProcessor است که دارای دو وابستگی تزریق شده‌ی به آن نیز می‌باشد:
        public LoanApplicationProcessor(
            IIdentityVerifier identityVerifier,
            ICreditScorer creditScorer)
        {
            _identityVerifier = identityVerifier ?? throw new ArgumentNullException(nameof(identityVerifier));
            _creditScorer = creditScorer ?? throw new ArgumentNullException(nameof(creditScorer));
        }
از این وابستگی‌ها برای تصدیق هویت درخواست کننده و همچنین بررسی میزان اعتبار او استفاده می‌شود.
تمام این منطق نیز در متد Process آن قابل مشاهده‌است که هدف اصلی آن، بررسی قابل پذیرش بودن درخواست یک وام جدید است.


نوشتن اولین تست، برای برنامه‌ی وام دهی

در اولین تصویر این قسمت، پروژه‌ی class library دومی را نیز به نام Loans.Tests مشاهده می‌کنید. فایل csproj آن به صورت زیر برای کار با MSTest تنظیم شده‌است:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\Loans\Loans.csproj" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
    <PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
    <PackageReference Include="MSTest.TestFramework" Version="2.0.0" />    
  </ItemGroup>
</Project>
که در آن ارجاعی به پروژه‌ی Loans.csproj و همچنین وابستگی‌های MSTest، تنظیم شده‌اند.

اکنون اولین آزمون واحد ما در کلاس جدید LoanApplicationProcessorShould چنین شکلی را پیدا می‌کند:
using Loans.Entities;
using Loans.Services;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {
        [TestMethod]
        public void DeclineLowSalary()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_100_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};
            var processor = new LoanApplicationProcessor(null, null);
            processor.Process(application);

            Assert.IsFalse(application.IsAccepted);
        }
    }
}
در حین کار با MSTest، کلاس آزمون واحد باید به ویژگی TestClass و متدهای public void آن به ویژگی TestMethod مزین شوند تا توسط این فریم‌ورک آزمون واحد شناسایی شده و مورد آزمایش قرار گیرند.
در این آزمایش، شخص درخواست کننده، حقوق کمی دارد و می‌خواهیم بررسی کنیم که آیا LoanApplicationProcessor می‌تواند آن‌را بر اساس مقدار MinimumSalary، رد کند یا خیر؟
public class LoanApplicationProcessor
{
    private const decimal MinimumSalary = 1_500_000_0;

در حین وهله سازی LoanApplicationProcessor، دو وابستگی آن به null تنظیم شده‌اند؛ چون می‌دانیم که بررسی MinimumSalary پیش از سایر بررسی‌ها صورت می‌گیرد و اساسا در این آزمایش، نیازی به این وابستگی‌ها نداریم.
اما اگر سعی در اجرای این آزمایش کنیم (برای مثال با اجرای دستور dotnet test در خط فرمان)، آزمایش اجرا نشده و با استثنای زیر مواجه می‌شویم:
Test method Loans.Tests.LoanApplicationProcessorShould.DeclineLowSalary threw exception:
System.ArgumentNullException: Value cannot be null.
Parameter name: identityVerifier
چون در سازنده‌ی کلاس LoanApplicationProcessor، در صورت نال بودن وابستگی‌های دریافتی، یک استثناء صادر می‌شود. بنابراین ذکر آن‌ها الزامی است:
        public LoanApplicationProcessor(
            IIdentityVerifier identityVerifier,
            ICreditScorer creditScorer)
        {
            _identityVerifier = identityVerifier ?? throw new ArgumentNullException(nameof(identityVerifier));
            _creditScorer = creditScorer ?? throw new ArgumentNullException(nameof(creditScorer));
        }


نصب کتابخانه‌ی Moq جهت برآورده کردن وابستگی‌های کلاس LoanApplicationProcessor

در این آزمایش چون وجود وابستگی‌های در سازنده‌ی کلاس، برای ما اهمیتی ندارند و همچنین ذکر آن‌ها نیز الزامی است، می‌خواهیم توسط کتابخانه‌ی Moq، دو نمونه‌ی تقلیدی از آن‌ها را تهیه کرده (همان dummies که پیشتر معرفی شدند) و جهت برآورده کردن بررسی صورت گرفته‌ی در سازنده‌ی کلاس LoanApplicationProcessor، آن‌ها را ارائه کنیم.
کتابخانه‌ی بسیار معروف Moq، با پروژه‌های مبتنی بر NETFramework 4.5. و همچنین NETStandard 2.0. به بعد سازگار است و برای نصب آن، می‌توان یکی از دو دستور زیر را صادر کرد:
> dotnet add package Moq
> Install-Package Moq

اما چرا کتابخانه‌ی Moq؟
کتابخانه‌ی Moq این اهداف را دنبال می‌کند: ساده‌است، به شدت کاربردی‌است و همچنین strongly typed است. این کتابخانه سورس باز بوده و تعداد بار دانلود بسته‌ی نیوگت آن میلیونی است.


پس از نصب آن، اولین آزمایشی را که نوشتیم، به صورت زیر اصلاح می‌کنیم:
using Loans.Entities;
using Loans.Services;
using Loans.Services.Contracts;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {
        [TestMethod]
        public void DeclineLowSalary()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_100_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};

            var mockIdentityVerifier = new Mock<IIdentityVerifier>();
            var mockCreditScorer = new Mock<ICreditScorer>();

            var processor = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object);
            processor.Process(application);

            Assert.IsFalse(application.IsAccepted);
        }
    }
}
در اینجا بجای ارسال null به سازنده‌ی کلاس LoanApplicationProcessor، جهت برآورده کردن مقدار پیش‌فرض پارامترهای آن و کامپایل شدن برنامه، نمونه‌های تقلیدی دو وابستگی مورد نیاز آن‌را تهیه و به آن ارسال کرده‌ایم.
کار با ذکر new Mock شروع شده و آرگومان جنریک آن‌را از نوع وابستگی‌هایی که نیاز داریم، مقدار دهی می‌کنیم. سپس خاصیت Object آن، امکان دسترسی به این شیء تقلید شده را میسر می‌کند.
اکنون اگر مجددا این آزمون واحد را اجرا کنیم، مشاهده خواهیم کرد که بجای صدور استثناء، با موفقیت به پایان رسیده‌است:



گاهی از اوقات جایگزین کردن یک وابستگی null با نمونه‌ی Mock آن کافی نیست

در مثالی که بررسی کردیم، اشیاء mock، کار برآورده کردن نیازهای ابتدایی آزمایش را انجام داده و سبب اجرای موفقیت آمیز آن شدند؛ اما همیشه اینطور نیست:
using Loans.Entities;
using Loans.Services;
using Loans.Services.Contracts;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace Loans.Tests
{
    [TestClass]
    public class LoanApplicationProcessorShould
    {        
        [TestMethod]
        public void Accept()
        {
            var product = new LoanProduct {Id = 99, ProductName = "Loan", InterestRate = 5.25m};
            var amount = new LoanAmount {CurrencyCode = "Rial", Principal = 2_000_000_0};
            var applicant =
                new Applicant {Id = 1, Name = "User 1", Age = 25, Address = "This place", Salary = 1_500_000_0};
            var application = new LoanApplication {Id = 42, Product = product, Amount = amount, Applicant = applicant};

            var mockIdentityVerifier = new Mock<IIdentityVerifier>();
            var mockCreditScorer = new Mock<ICreditScorer>();

            var processor = new LoanApplicationProcessor(mockIdentityVerifier.Object, mockCreditScorer.Object);
            processor.Process(application);

            Assert.IsTrue(application.IsAccepted);
        }
    }
}
تفاوت این آزمایش جدید با قبلی، در دو مورد است: مقدار Salary به MinimumSalary تنظیم شده‌است و در آخر Assert.IsTrue را داریم.
اگر این آزمایش را اجرا کنیم، با شکست مواجه خواهد شد. علت اینجا است که هرچند در حال استفاده‌ی از دو mock object به عنوان وابستگی‌های مورد نیاز هستیم، اما تنظیمات خاصی را بر روی آن‌ها انجام نداده‌ایم و به همین جهت خروجی مناسبی را در اختیار LoanApplicationProcessor قرار نمی‌دهند. برای مثال مرحله‌ی بعدی بررسی اعتبار شخص در کلاس LoanApplicationProcessor، فراخوانی سرویس identityVerifier و متد Validate آن است که خروجی آن بر اساس کدهای فعلی، همیشه false است:
_identityVerifier.Initialize();
var isValidIdentity = _identityVerifier.Validate(
    application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);
در قسمت بعدی، کار تنظیم اشیاء mock را انجام خواهیم داد.

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MoqSeries-01.zip
مطالب دوره‌ها
ایجاد یک کلاس جدید پویا و وهله‌ای از آن در زمان اجرا توسط Reflection.Emit
توانایی‌های Reflection.Emit صرفا به ایجاد متدهایی کاملا جدید و پویا در زمان اجرا محدود نمی‌شود. برای نمونه کلاس ذیل را درنظر بگیرید:
    public class Person
    {
        private string _name;
        public string Name
        {
            get { return _name; }
        }

        public Person(string name)
        {
            _name = name;
        }
    }
در ادامه قصد داریم معادل این کلاس را به همراه وهله‌ای از آن، به صورتی کاملا پویا در زمان اجرا ایجاد کرده (تصور کنید این کلاس در برنامه وجود خارجی نداشته و تنها جهت درک بهتر کدهای IL ادامه بحث، معرفی گردیده است) و سپس مقداری را به سازنده آن ارسال کنیم.
کدهای کامل و توضیحات این typeBuilder را در ادامه ملاحظه می‌کنید:
using System;
using System.Reflection;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static void Main(string[] args)
        {
            //اسمبلی محل قرارگیری کدهای پویای نهایی در اینجا تعیین می‌شود
            //حالت دسترسی به آن اجرایی درنظر گرفته شده، امکان تعیین حالت‌های دیگری مانند ذخیره سازی نیز وجود دارد
            var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
                                      name: new AssemblyName("Demo"), access: AssemblyBuilderAccess.Run);

            // اکنون داخل این اسمبلی یک ماژول جدید را برای قرار دادن کلاس جدید خود تعریف می‌کنیم
            var moduleBuilder = assemblyBuilder.DefineDynamicModule(name: "PersonModule");

            // کار ساخت نوع و کلاس جدید شخص عمومی از اینجا شروع می‌شود
            var typeBuilder = moduleBuilder.DefineType(name: "Person", attr: TypeAttributes.Public);

            // افزودن فیلد خصوصی نام تعریف شده در سطح کلاس شخص
            var nameField = typeBuilder.DefineField(fieldName: "_name",
                                                    type: typeof(string),
                                                    attributes: FieldAttributes.Private);

            // تعریف سازنده عمومی کلاس شخص که دارای یک آرگومان رشته‌ای است
            var ctor = typeBuilder.DefineConstructor(
                                    attributes: MethodAttributes.Public,
                                    callingConvention: CallingConventions.Standard,
                                    parameterTypes: new[] { typeof(string) });
            // تعریف بدنه سازنده کلاس شخص
            // در اینجا فیلد خصوصی تعریف شده در سطح کلاس باید مقدار دهی شود
            var ctorIL = ctor.GetILGenerator();
            // نکته‌ای در مورد سازنده‌ها
            ctorIL.Emit(OpCodes.Ldarg_0); // اندیس صفر در سازنده کلاس به وهله‌ای از کلاس جاری اشاره می‌کند
            ctorIL.Emit(OpCodes.Ldarg_1); // بارگذاری آرگومان سازنده و قرار دادن آن روی پشته
            // مقدار دهی فیلد خصوصی نام که به وهله‌ای از کلاس جاری و مقدار آرگومان دریافتی نیاز دارد
            ctorIL.Emit(OpCodes.Stfld, nameField);
            ctorIL.Emit(OpCodes.Ret); // پایان کار سازنده

            // تعریف خاصیت رشته‌ای نام در کلاس شخص
            var nameProperty = typeBuilder.DefineProperty(
                                                name: "Name",
                                                attributes: PropertyAttributes.HasDefault,
                                                returnType: typeof(string),
                                                parameterTypes: null); // خاصیت پارامتر ورودی ندارد

            var namePropertyGetMethod = typeBuilder.DefineMethod(
                                                name: "get_Name",
                                                attributes: MethodAttributes.Public |
                //متد ویژه‌ای است که توسط کامپایلر پردازش و تشخیص داده می‌شود
                                                            MethodAttributes.SpecialName |
                                                            MethodAttributes.HideBySig,
                                                returnType: typeof(string),
                                                parameterTypes: Type.EmptyTypes);
            // اتصال گت متد به خاصیت رشته‌ای نام که پیشتر تعریف شد
            nameProperty.SetGetMethod(namePropertyGetMethod);

            // بدنه گت متد در اینجا تعریف خواهد شد
            var namePropertyGetMethodIL = namePropertyGetMethod.GetILGenerator();
            namePropertyGetMethodIL.Emit(OpCodes.Ldarg_0); // بارگذاری اشاره‌گری به وهله‌ای از کلاس جاری در پشته
            namePropertyGetMethodIL.Emit(OpCodes.Ldfld, nameField); // بارگذاری فیلد نام
            namePropertyGetMethodIL.Emit(OpCodes.Ret);

            var t = typeBuilder.CreateType(); // نهایی سازی کار ایجاد نوع جدید

            // ایجاد وهله‌ای از نوع جدید که پارامتری رشته‌ای به سازنده آن ارسال می‌شود
            var instance = Activator.CreateInstance(t, "Vahid");

            // دسترسی به خاصیت نام
            var nProperty = t.GetProperty("Name");
            // و دریافت مقدار آن برای نمایش
            var result = nProperty.GetValue(instance, null);

            Console.WriteLine(result);
        }
    }
}
در اینجا ایجاد یک کلاس جدید با ایجاد یک TypeBuilder واقع در فضای نام  System.Reflection.Emit آغاز می‌شود. پیش از آن نیاز است یک اسمبلی پویا و ماژولی در آن‌را برای قرار دادن کدهای پویای این TypeBuilder ایجاد کنیم. توضیحات مرتبط با دستورات مختلف را به صورت کامنت در کدهای فوق ملاحظه می‌کنید. با استفاده از TypeBuilder و متد DefineField آن می‌توان یک فیلد در سطح کلاس ایجاد کرد و یا توسط متد DefineConstructor آن، سازنده کلاس را با امضایی ویژه تعریف نمود و سپس با دسترسی به ILGenerator آن، بدنه این سازنده را همانند متدهای پویا ایجاد کرد.
اگر به کدهای فوق دقت کرده باشید، متد get_Name به خاصیت Name انتساب داده شده است. علت را در قسمت معرفی اجمالی Reflection زمانیکه لیست متدهای کلاس Person را نمایش دادیم، ملاحظه کرده‌اید. تمام خواص Auto implemented در دات نت، هر چند ظاهر ساده‌ای دارند اما در عمل به دو متد get_Name و set_Name در کدهای IL توسط کامپایلر تبدیل می‌شوند. به همین جهت در اینجا نیاز بود تا get_Name را نیز تعریف کنیم.


چند مثال تکمیلی
Populating a PropertyGrid using Reflection.Emit
Dynamically adding RaisePropertyChanged to MVVM Light ViewModels using Reflection.Emit
مطالب
اضافه کردن پیوست به فایل‌های PDF با استفاده از iTextSharp

فایل PDF موجود عجیب و غریبی است. می‌شود به آن فایل پیوست اضافه کرد. مثلا اگر یک راهنمای آموزشی را با فرمت PDF تهیه می‌کنید، لازم نیست تا فایل‌های مرتبط با آن‌را جداگانه ارائه دهید. می‌شود تمام این‌ها را داخل همان فایل PDF مدفون کرد. روش انجام اینکار به کمک iTextSharp ساده است اما چند نکته را نیز به همراه دارد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PDFAttachment
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();

pdfDoc.Add(new Phrase("Test"));

var fs = PdfFileSpecification.FileEmbedded(pdfWriter, @"C:\path\logo.png", "logo.png", null);
pdfWriter.AddFileAttachment("توضیحات",fs);
}

Process.Start("Test.pdf");
}
}
}

در ساده‌ترین حالت ممکن، با استفاده از متد AddFileAttachmen شیء PdfWriter می‌توان پیوستی را به یک فایل PDF در حال تولید اضافه کرد. اگر به فایل نهایی مراجعه کنیم و همچنین قسمت attachments را هم دستی در Adobe reader انتخاب نمائیم، شکل زیر حاصل خواهد شد:


روش متداول بکارگرفته شده دو مشکل را به همراه دارد:
  • قسمت modified مقدار دهی نشده است.
  • پنل مربوط به پیوست‌ها باید دستی باز شود.

نحوه مقدار دهی ستون modified پس از تعریف یک PdfDictionary و قرار دادن PdfName.MODDATE در آن، به صورت زیر است:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PDFAttachment
{
class Program
{
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();

pdfDoc.Add(new Phrase("Test"));

var filePath = @"C:\path\logo.png";
var fileInfo = new FileInfo(filePath);
var pdfDictionary = new PdfDictionary();
pdfDictionary.Put(PdfName.MODDATE, new PdfDate(fileInfo.LastWriteTime));
var fs = PdfFileSpecification.FileEmbedded(pdfWriter, filePath, fileInfo.Name, null, true, null, pdfDictionary);
pdfWriter.AddFileAttachment("توضیحات", fs);
}

Process.Start("Test.pdf");
}
}
}

که اینبار خروجی زیر را به همراه دارد:


و برای نمایش خودکار پنل پیوست‌ها در Adobe reader به طوری که کاربر نهایی متوجه وجود این فایل‌های پیوست شده گردد، می‌توان ViewerPreferences شیء pdfWriter را مقدار دهی نمود:

pdfWriter.ViewerPreferences = PdfWriter.PageModeUseAttachments;

در مورد فایل‌های موجود چطور؟ آیا می‌توان به یک فایل PDF از پیش تهیه شده هم فایل پیوست کرد؟
پاسخ: بله. باید از امکانات شیء PdfReader استفاده کرد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PDFAttachment
{
class Program
{
static void Main(string[] args)
{
var reader = new PdfReader("Test.pdf");
using (var stamper = new PdfStamper(reader, new FileStream("newTest.pdf", FileMode.Create)))
{
var filePath = @"C:\path\logo.png";
addAttachment(stamper, filePath, "توضیحات");
stamper.Close();
}

Process.Start("newTest.pdf");
}

private static void addAttachment(PdfStamper stamper, string filePath, string description)
{
var fileInfo = new FileInfo(filePath);
var pdfDictionary = new PdfDictionary();
pdfDictionary.Put(PdfName.MODDATE, new PdfDate(fileInfo.LastWriteTime));
var pdfWriter = stamper.Writer;
var fs = PdfFileSpecification.FileEmbedded(pdfWriter, filePath, fileInfo.Name, null, true, null, pdfDictionary);
stamper.AddFileAttachment(description, fs);
}
}
}

در اینجا به کمک کلاس PdfReader، یک فایل موجود خوانده شده و سپس با استفاده از امکانات کلاس PdfStamper که خاصیت Writer آن همان pdfWriter است می‌توان فایل مورد نظر را به فایل موجود افزود.


نظرات مطالب
OpenCVSharp #6
برای کار با وب کم و دوربین دیجیتال، این مقدار را باید محاسبه کرد (شمارش تعداد فریم دریافتی در طی حداقل 3 ثانیه):
private static double getFps(CvCapture capture)
{
    double counter = 0;
    double seconds = 0;
    var watch = Stopwatch.StartNew();
    while (capture.QueryFrame() != null)
    {
        counter++;
        seconds = watch.ElapsedMilliseconds / (double)1000;
        if (seconds >= 3)
        {
            watch.Stop();
            break;
        }
    }
    var fps = counter / seconds;
    return fps;
}
و بعد برای استفاده:
using (var capture = CvCapture.FromCamera(index: 0))
{
    var fps = getFps(capture);
    capture.SetCaptureProperty(CvConst.CV_CAP_PROP_FPS, fps);
    var interval = (int)(1000 / fps);
مطالب
StringBuilder

بهترین روش برای تولید و دستکاری یک رشته (string) طولانی و یا دستکاری متناوب و تکراری یک رشته استفاده از کلاس StringBuilder است. این کلاس در فضای نام System.Text قرار داره. شی String در دات‌نت‌فریمورک تغییرناپذیره (immutable)، بدین معنی که پس از ایجاد نمی‌توان محتوای اونو تغییر داد. برای مثال اگر شما بخواین محتوای یک رشته رو با اتصال به رشته‌ای دیگه تغییر بدین، اجازه اینکار را به شما داده نمی‌شه. درعوض به‌صورت خودکار رشته‌ای جدید در حافظه ایجاد میشه و محتوای دو رشته موجود پس از اتصال به هم درون اون قرار می‌گیره. این کار درصورتی‌که تعداد عملیات مشابه زیاد باشه می‌تونه تاثیر منفی بر کارایی و حافظه خالی در دسترس برنامه بگذاره.

کلاس StringBuilder با استفاده از آرایه‌ای از کاراکترها، راه‌حل مناسب و بهینه‌ای رو برای این مشکل فراهم کرده. این کلاس در زمان اجرا به شما اجازه می‌ده تا بدون ایجاد نمونه‌های جدید از کلاس String، محتوای یک رشته رو تغییر بدین. شما می‌تونید نمونه‌ای از این کلاس رو به‌صورت خالی و یا با یک رشته اولیه ایجاد کنید، سپس با استفاده از متدهای متنوع موجود، محتوای رشته رو با استفاده از انواع داده مختلف و به‌صورت دلخواه دستکاری کنید. هم‌چنین با استفاده از متد معروف  ()ToString این کلاس می‌تونید در هر لحظه دلخواه رشته تولیدی رو بدست بیارین. دو پراپرتی مهم کلاس StringBuilder رفتارش رو درهنگام افزودن داده‌های جدید کنترل می‌کنن:

Capacity , Length

پراپرتی Capacity اندازه بافر کلاس StringBuilder را تعیین می‌کنه و Length طول رشته جاری موجود در این بافر رو نمایش می‌ده. اگر پس از افزودن داده جدید، طول رشته از اندازه بافر موجود بیشتر بشه، StringBuilder باید یه بافر جدید با اندازه‌ای مناسب ایجاد کنه تا رشته جدید رو بتونه تو خودش نگه داره. اندازه این بافر جدید به‌صورت پیش‌فرض دو برابر اندازه بافر قبلی درنظر گرفته می‌شه. بعد تمام رشته قبلی رو تو این بافر جدید کپی میکنه.

از برنامه ساده زیر میتونین برای بررسی این مسئله استفاده کنین:

using System.IO;
using System.Text;

class Program
{
  static void Main()
  {
    using (var writer = new StreamWriter("data.txt"))
    {
      var builder = new StringBuilder();
      for (var i = 0; i <= 256; i++)
      {
        writer.Write(builder.Capacity);
        writer.Write(",");
        writer.Write(builder.Length);
        writer.WriteLine();
        builder.Append('1'); // <-- Add one character
      }
    }
  }
}

دقت کنین که برای افزودن یک کاراکتر استفاده از دستور Append با نوع داده char (همونطور که در بالا استفاده شده) بازدهی بهتری نسبت به استفاده از نوع داده string (با یک کاراکتر) داره. خروجی کد فوق به صورت زیره:

16, 0
16, 1
16, 2
...
16,14
16,15
16,16
32,17
...

استفاده نامناسب و بی‌دقت از این کلاس می‌تونه منجر به بازسازی‌های متناوب این بافر شده که درنهایت فواید کلاس StringBuilder رو تحت تاثیر قرار میده. درهنگام کار با کلاس StringBuilder اگر از طول رشته موردنظر و یا حد بالایی برای Capacity آن آگاهی حتی نسبی دارین، می‌تونید با مقداردهی مناسب این پراپرتی از این مشکل پرهیز کنید.

نکته: مقدار پیش‌فرض پراپرتی Capacity برابر 16 است.

هنگام مقداردهی پراپرتی‌های Capacity یا Length به موارد زیر توجه داشته باشید:

- مقداردهی Capacity به میزانی کمتر از طول رشته جاری (پراپرتی Length)، منجر به خطای زیر می‌شه:

System.ArgumentOutOfRangeException

خطای مشابهی هنگام مقداردهی پراپرتی Capacityبه بیش از مقدار پراپرتی MaxCapacity رخ می‌دهه.البته این مورد تنها درصورتی‌که بخواین اونو به بیش از حدود 2 گیگابایت (Int32.MaxValue) مقداردهی کنید پیش میاد!

- اگر پراپرتی Length را به مقداری کمتر از طول رشته جاری تنظیم کنید، رشته به اندازه طول تنظیمی کوتاه (truncate) میشه.

- اگر مقدار پراپرتی Length را به میزانی بیشتر از طول رشته جاری تنظیم کنید، فضای خالی موجود در بافر با space پر میشه.

- تنظیم مقدار Length بیشتر از Capacity، منجر به مقداردهی خودکار پراپرتی Capacity به مقدار جدید تنظیم شده برای Length میشه.

در ادامه به یک مثال برای مقایسه کارایی تولید یک رشته طولانی با استفاده از این کلاس میپردازیم. تو این مثال از دو روش برای تولید رشته‌های طولانی استفاده میشه. روش اول که همون روش اتصال رشته‌ها (Concat) به هم هستش و روش دوم هم که استفاده از کلاس StringBuilder است. در قطعه کد زیر کلاس مربوط به عملیات تست رو مشاهده میکنین:

namespace StringBuilderTest
{
  internal class SbTest1
  {
    internal Action<string> WriteLog;
    internal int Iterations { get; set; }
    internal string TestString { get; set; }

    internal SbTest1(int iterations, string testString, Action<string> writeLog)
    {
      Iterations = iterations;
      TestString = testString;
      WriteLog = writeLog;
    }

    internal void StartTest()
    {
      var watch = new Stopwatch();

      //StringBuilder
      watch.Start();
      var sbTestResult = SbTest();
      watch.Stop();
      WriteLog(string.Format("StringBuilder time: {0}", watch.ElapsedMilliseconds));

      //Concat
      watch.Start();
      var concatTestResult = ConcatTest();
      watch.Stop();
      WriteLog(string.Format("ConcatTest time: {0}", watch.ElapsedMilliseconds));

      WriteLog(string.Format("Results are{0} the same", sbTestResult == concatTestResult ? string.Empty : " NOT"));
    }

    private string SbTest()
    {
      var sb = new StringBuilder(TestString);
      for (var x = 0; x < Iterations; x++)
      {
        sb.Append(TestString);
      }
      return sb.ToString();
    }

    private string ConcatTest()
    {
      string concat = TestString;
      for (var x = 0; x < Iterations; x++)
      {
        concat += TestString;
      }
      return concat;
    }
  }
}

دو روش بحث‌شده در کلاس مورد استفاده قرار گرفته و مدت زمان اجرای هر کدوم از عملیات‌ها به خروجی فرستاده میشه. برای استفاده از این کلاس هم میشه از کد زیر در یک برنامه کنسول استفاده کرد:

do
{
  Console.Write("Iteration: ");
  var iterations = Convert.ToInt32(Console.ReadLine());
  Console.Write("Test String: ");
  var testString = Console.ReadLine();
  var test1 = new SbTest1(iterations, testString, Console.WriteLine);
  test1.StartTest();
  Console.WriteLine("----------------------------------------------------------------");
} while (Console.ReadKey(true).Key == ConsoleKey.C); // C = continue 

برای نمونه خروجی زیر در لپ‌تاپ من (Corei7 2630QM) بدست اومد:

تنظیم خاصیت Capacity به یک مقدار مناسب میتونه تو کارایی تاثیرات زیادی بگذاره. مثلا در مورد مثال فوق میشه یه متد دیگه برای آزمایش تاثیر این مقداردهی به صورت زیر به کلاس برناممون اضافه کنیم:

private string SbCapacityTest()
{
  var sb = new StringBuilder(TestString) { Capacity = TestString.Length * Iterations };
  for (var x = 0; x < Iterations; x++)
  {
    sb.Append(TestString);
  }
  return sb.ToString();
}

تو این متد قبل از ورود به حلقه مقدار خاصیت Capacity به میزان موردنظر تنظیم شده و نتیجه بدست اومده:

مشاهده میشه که روش concat خیلی کنده (دقت کنین که طول رشته اولیه هم بیشتر شده) و برای ادامه کار مقایسه اون رو کامنت کردم و نتایج زیر بدست اومد:

می‌بینین که استفاده مناسب از مقداردهی به خاصیت Capacity میتونه تا حدود 300 درصد سرعت برنامه ما رو افزایش بده. البته همیشه اینطوری نخواهد بود. ما در این مثال مقدار دقیق طول رشته نهایی رو میدونستیم که باعث میشه عملیات افزایش بافر کلاس StringBuilder هیچوقت اتفاق نیفته. این امر در واقعیت کمتر پیش میاد.

مقاله موجود در سایت dotnetperls شکل زیر رو به عنوان نتیجه تست بازدهی ارائه میده:

- در مواقعی که عملیاتی همچون مثال بالا طولانی و حجیم ندارین بهتره که از این کلاس استفاده نکنین چون عملیات‌های داخلی این کلاس در عملیات کوچک و سبک (مثل ابتدای نمودار فوق) موجب کندی عملیات میشه. همچنین استفاده از اون نیاز به کدنویسی بیشتری داره.

- این کلاس فشار کمتری به حافظه سیستم وارد میکنه. درمقابل استفاده از روش concat موجب اشغال بیش از حد حافظه میشه که خودش باعث اجرای بیشتر و متناوب‌تر GC میشه که در نهایت کارایی سیستم رو کاهش میده.

- استفاده از این کلاس برای عملیات Replace (و یا عملیات مشابه) در حلقه‌ها جهت کار با رشته‌های طولانی و یا تعداد زیادی رشته میتونه بسیار سریعتر و بهتر عمل کنه چون این کلاس برخلاف کلاس string اشیای جدید تولید نمیکنه.

- یه اشتباه بزرگ در استفاده از این کلاس استفاده از "+" برای اتصال رشته‌های درون StringBuilder هست. هرگز از این کارها نکنین. (فکر کنم واضحه که چرا)

مطالب
استفاده از AvalonEdit در WPF
AvalonEdit یکی از زیرساخت‌های برنامه‌ی SharpDevelop است که ویرایشگر متنی به همراه syntax highlighting زبان‌های مختلف را در آن پشتیبانی می‌کند. کیفیت بالایی داشته و بسیاری از برنامه‌های دیگر نیز از آن جهت ارائه ویرایشگر و یا syntax highlighting متون ارائه شده، استفاده می‌کنند. در ادامه نحوه‌ی استفاده از این ویرایشگر را در برنامه‌های WPF خصوصا با دید MVVM بررسی خواهیم کرد.



دریافت و نصب AvalonEdit

برای نصب AvalonEdit می‌توان دستور ذیل را در کنسول پاورشل نیوگت صادر کرد:
 PM> install-package AvalonEdit


استفاده‌ی مقدماتی از AvalonEdit

برای استفاده از این ویرایشگر ابتدا نیاز است فضای نام xmlns:avalonEdit تعریف شود. سپس کنترل avalonEdit:TextEditor در دسترس خواهد بود:
<Window x:Class="SyntaxHighlighter.MainWindow"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
   Title="MainWindow" Height="401" Width="617">
  <Grid>
<avalonEdit:TextEditor  
    Name="txtCode"
    SyntaxHighlighting="C#"
  FontFamily="Consolas"
  FontSize="10pt"/>   
  </Grid>
</Window>
توسط خاصیت SyntaxHighlighting آن می‌توان زبان مشخصی را تعریف کرد. لیست زبان‌های توکار پشتیبانی شده


استفاده از AvalonEdit در برنامه‌های MVVM

خاصیت Text این ویرایشگر به صورت معمولی تعریف شده (DependencyProperty نیست) و امکان binding دو طرفه به آن وجود ندارد. به همین جهت نیاز است یک چنین DependencyProperty را به آن اضافه کرد:
using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Windows;
using System.Xml;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;

namespace AvalonEditWpfTest.Controls
{
    public class BindableAvalonTextEditor : TextEditor
    {
        public static readonly DependencyProperty BoundTextProperty =
            DependencyProperty.Register("BoundText",
                typeof(string),
                typeof(BindableAvalonTextEditor),
                new FrameworkPropertyMetadata(default(string), propertyChangedCallback));
 
        public static string GetBoundText(DependencyObject obj)
        {
            return (string)obj.GetValue(BoundTextProperty);
        }

        public static void SetBoundText(DependencyObject obj, string value)
        {
            obj.SetValue(BoundTextProperty, value);
        }

        protected override void OnTextChanged(EventArgs e)
        {
            SetCurrentValue(BoundTextProperty, Text);
            base.OnTextChanged(e);
        }

        private static void propertyChangedCallback(DependencyObject obj,
                                    DependencyPropertyChangedEventArgs args)
        {
            var target = (BindableAvalonTextEditor)obj;
            var value = args.NewValue;
            if (value == null)
                return;

            if (string.IsNullOrWhiteSpace(target.Text) ||
                !target.Text.Equals(args.NewValue.ToString()))
            {
                target.Text = args.NewValue.ToString();
            }
        }
    }
}
کار با ارث بری از TextEditor (ویرایشگر AvalonEdit) شروع می‌شود. سپس یک DependencyProperty به نام BoundText در اینجا اضافه شده‌است. هر زمان که متن داخل آن تغییر کرد، آن‌را به خاصیت متنی Text این ویرایشگر نسبت می‌دهد. به این ترتیب binding یک طرفه (از کدها به کنترل) کار می‌کند. فعال سازی binding دو طرفه با پشتیبانی از انتقال تغییرات از ویرایشگر به خواص ViewModel در متد بازنویسی شده‌ی OnTextChanged انجام می‌شود.

اکنون برای استفاده از این کنترل جدید که BindableAvalonTextEditor نام دارد، می‌توان به نحو ذیل عمل کرد:
<Window x:Class="AvalonEditWpfTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewModels="clr-namespace:AvalonEditTests.ViewModels"
        xmlns:controls="clr-namespace:AvalonEditWpfTest.Controls"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <viewModels:MainWindowViewModel x:Key="MainWindowViewModel"/>
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource MainWindowViewModel}}">
        <controls:BindableAvalonTextEditor
                BoundText="{Binding SourceCode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                        WordWrap="True"
                        ShowLineNumbers="True"
                        LineNumbersForeground="MediumSlateBlue"
                        FontFamily="Consolas"
                        VerticalScrollBarVisibility="Auto"
                        Margin="3"          
                        HorizontalScrollBarVisibility="Auto"
                        FontSize="10pt"/>
    </Grid>
</Window>
ابتدا فضای نام جدید کنترل BindableAvalonTextEditor مشخص می‌شود و سپس به controls:BindableAvalonTextEditor دسترسی خواهیم داشت. در اینجا نحوه‌ی استفاده از خاصیت جدید BoundText را نیز مشاهده می‌کنید.


افزودن syntax highlighting زبان‌هایی که به صورت رسمی پشتیبانی نمی‌شوند

به خاصیت SyntaxHighlighting این کنترل صرفا مقادیری را می‌توان نسبت داد که به صورت توکار پشتیبانی می‌شوند. برای مثال#XML، C و امثال آن.
فرض کنید نیاز است SyntaxHighlighting زبان SQL را فعال کنیم. برای اینکار نیاز به فایل‌های ویژه‌ای است، با پسوند xshd. برای نمونه فایل sql-ce.xshd را در اینجا می‌توانید مطالعه کنید. در آن یک سری واژه‌های کلیدی و حروفی که باید با رنگی متفاوت نمایش داده شوند، مشخص می‌گردند.
برای استفاده از فایل sql-ce.xshd باید به نحو ذیل عمل کرد:
الف) فایل sql-ce.xshd را به پروژه اضافه کرده و سپس در برگه‌ی خواص آن در VS.NET، مقدار build action آن‌را به embedded resource تغییر دهید.



ب) با استفاده از متد ذیل، این فایل مدفون شده در اسمبلی را گشوده و به متد HighlightingLoader.Load ارسال می‌کنیم:
        private static IHighlightingDefinition getHighlightingDefinition(string resourceName)
        {
            if (string.IsNullOrWhiteSpace(resourceName))
                throw new NullReferenceException("Please specify SyntaxHighlightingResourceName.");

            using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
            {
                if (stream == null)
                    throw new NullReferenceException(string.Format("{0} resource is null.", resourceName));

                using (var reader = new XmlTextReader(stream))
                {
                    return HighlightingLoader.Load(reader, HighlightingManager.Instance);
                }
            }
        }
نحوه استفاده از آن نیز به صورت ذیل است:
 txtCode.SyntaxHighlighting = getHighlightingDefinition(resourceName);
به این ترتیب می‌توان یک فایل xhsd را به صورت پویا بارگذاری و به خاصیت SyntaxHighlighting کنترل انتساب داد.

برای سهولت استفاده از این قابلیت شاید بهتر باشد یک DependencyProperty دیگر به نام SyntaxHighlightingResourceName را به کنترل جدید BindableAvalonTextEditor اضافه کنیم:
using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Windows;
using System.Xml;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;

namespace AvalonEditWpfTest.Controls
{
    public class BindableAvalonTextEditor : TextEditor
    {
         public static readonly DependencyProperty SyntaxHighlightingResourceNameProperty =
           DependencyProperty.Register("SyntaxHighlightingResourceName",
               typeof(string),
               typeof(BindableAvalonTextEditor),
               new FrameworkPropertyMetadata(default(string), resourceNamePropertyChangedCallback));
 
        public static string GetSyntaxHighlightingResourceName(DependencyObject obj)
        {
            return (string)obj.GetValue(SyntaxHighlightingResourceNameProperty);
        }

        public static void SetSyntaxHighlightingResourceName(DependencyObject obj, string value)
        {
            obj.SetValue(SyntaxHighlightingResourceNameProperty, value);
        }

        private static void loadHighlighter(TextEditor @this, string resourceName)
        {
            if (@this.SyntaxHighlighting != null)
                return;

            @this.SyntaxHighlighting = getHighlightingDefinition(resourceName);
        }

        private static void resourceNamePropertyChangedCallback(DependencyObject obj,
                                            DependencyPropertyChangedEventArgs args)
        {
            var target = (BindableAvalonTextEditor)obj;
            var value = args.NewValue;
            if (value == null)
                return;

            loadHighlighter(target, value.ToString());
        }
    }
}
کاری که در اینجا انجام شده، افزودن یک خاصیت جدید به نام SyntaxHighlightingResourceName به کنترل BindableAvalonTextEditor است. هر زمانیکه مقدار آن تغییر کند، متد getHighlightingDefinition بحث شده، فراخوانی گردیده و به صورت پویا مقدار خاصیت SyntaxHighlighting این کنترل، مقدار دهی می‌شود.
استفاده از آن نیز به شکل زیر است:
   <controls:BindableAvalonTextEditor
SyntaxHighlightingResourceName = "AvalonEditWpfTest.Controls.sql-ce.xshd"
/>

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
AvalonEditWpfTest.zip 
مطالب
Ef 6 و Ngen : شروعی سریعتر برای برنامه های مبتنی بر Entity Framework
تولید کد Native زمانی اتفاق می‌افتد که کامپایلر JIT، کد اسمبلی‌های MSIL را به کدهای Native در ماشین محلی کامپایل می‌کند و این عمل بلافاصله قبل از اجرای متد برای اولین بار اتفاق می‌افتد. این کد به صورت موقتی بوده و در حافظه‌ای که برای پردازش در نظر گرفته شده ذخیره می‌شود و در پایان هر پردازش توسط سیستم عامل ویرایش می‌شود. کد Native به ازای هر بار شروع یک پردازش تولید می‌شود. ابزار Native Image Generator یا همان Ngen اقدام به تولید کد Native با استفاده از کامپایلر JIT نموده و آن را در هارد دیسک ذخیره می‌نماید. زمانیکه برنامه نیازمند یک اسمبلی CLR است، به جای بارگذاری خود اسمبلی، ایمیج کد Native آن بارگذاری می‌شود. به این نکته نیز توجه داشته باشید که CLR اطلاعاتی در مورد اینکه کدام اسمبلی، ایمیج کد Native است و این ایمیج در کجا و در چه زمانی تهیه شده است، دارد. کد Native باعث بهبود استفاده از حافظه می‌شود، زمانیکه یک اسمبلی بین پروسس‌ها به اشتراک گذاشته شده‌است. تا قبل از EF6 کتابخانه‌های هسته‌ای EF در زمان اجرا جزئی از دات نت فریمورک بودند و تولید کد Native آنها به صورت اتوماتیک انجام می‌شد. اما از نسخه 6، تمامی این کتابخانه‌ها در داخل پکیج Nuget آن ترکیب شده‌اند . پس برای تولید کد Native مربوط به فایل EntityFramework.dll نیازمند ابزار Ngen هستیم.
1- ابتدا یک برنامه‌ی ساده کنسول ویندوز ساخته و از Package Manager Console دستور Install-package entityframework را اجرا نموده تا پکیج Ef به برنامه اضافه گردد.
using System;
using System.Data.Entity;

namespace UsingNgen
{

    public class NgenDbContex : DbContext
    { }

    class Program
    {
        static void Main()
        {
            var nGenCtx = new NgenDbContex();
            Console.WriteLine("Press a key to exit...");
            Console.ReadKey();
        }
    }


}
حال کد ساده بالا را به برنامه اضافه می‌کنیم و برنامه را Build میکنیم.
2- برای ثبت جزئیات اجرای برنامه از ابزار Windows Performance Recorder که جزئی از ویندوز می‌باشد، استفاده می‌کنیم. کافیست عبارت WPR را در نوار جستجوی ویندوز تایپ کنید تا این ابزار در دسترس قرار گیرد. 



برای ضبط جزئیات، روی دکمه‌ی Start کلیک کنید و به محل ذخیره‌ی فایل اجرایی حاصل از Build ویژوال استودیو رفته و آن را اجرا کنید. بعد از اتمام اجرا، جزئیات را ذخیره نمایید.

بعد از ذخیره فایل، در پنجره بالا دکمه‌ای به نام Open in WPA ظاهر می‌شود. WPA مخفف Windows Performance Analyzer می‌باشد. آن را کلیک کنید تا محیط آنالایزر باز شود.

حال در سمت چپ این پنجره انواع آنالایزرها را مشاهده می‌کنید. روی آنالایزر Computation کلیک کنید و از زیرمجموعه‌ی آن، CPU Usage را انتخاب کنید. آمار مربوط به برنامه خودمان را در تصویر بالا مشاهده می‌کنید. کل برنامه 164 میلی ثانیه زمان برده و فایل Clr.dll حدود 47 میلی ثانیه و یک فایل clrjit.dll نیز برای تولید کد JIT وجود دارد. حال برای تسریع در عمل شروع، از تکنیک Ngen به صورت زیر استفاده می‌کنیم.

3- دوباره به نوار جستجوی ویندوز رفته و ابزار Developer Command Prompt for VsXXXX را با امتیاز دسترسی از نوع Admin اجرا کنید. XXXX نسخه‌ی ویژوال استودیو می‌باشد.

حال به محل ذخیره فایل اجرایی برنامه رفته و دستور Ngen Install EntityFramework.dll را تایپ کنید تا یک ایمیج کد Native از entityframework.dll ساخته شود. دوباره ابزار Windows Performance Recorder را لود کرده و روی دکمه Start کلیک کنید و فایل اجرایی برنامه را اجرا نمایید. پس از اتمام عملیات ثبت جزئیات، آن را در Windows Performance Analyzer باز نمایید.

همانطور که مشاهده می‌کنید کل برنامه ما 89 میلی ثانیه زمان برده و Clr.dll 29 ثانیه و به جای clrjit.dll فایل EntityFramework به صورت native تولید شده است.

نظرات مطالب
خواندن اطلاعات از فایل اکسل با استفاده از LinqToExcel
برای دیباگ فایل‌های اکسل از کتابخانه‌ی EPPlus هم می‌توانید استفاده کنید:
using System;
using System.IO;
using OfficeOpenXml;

namespace ExcelDataReader
{
    class Program
    {
        /// <summary>
        /// PM> Install-Package EPPlus
        /// </summary>
        static void Main(string[] args)
        {
            var filePath = "sample.xlsx";
            var fileInfo = new FileInfo(filePath);
            if (!fileInfo.Exists)
            {
                throw new FileNotFoundException($"{filePath} file not found.");
            }

            var worksheetName = "Sheet1";
            using (var package = new ExcelPackage(fileInfo))
            {
                var worksheet = package.Workbook.Worksheets[worksheetName];
                var startCell = worksheet.Dimension.Start;
                var endCell = worksheet.Dimension.End;

                for (var row = startCell.Row; row < endCell.Row + 1; row++)
                {
                    for (var col = startCell.Column; col <= endCell.Column; col++)
                    {
                        var header = worksheet.Cells[1, col].Value ?? worksheet.Cells[2, col].Value;
                        var name = header?.ToString();
                        var value = worksheet.Cells[row, col].Value;
                        //var intValue = Convert.ChangeType(value, typeof(int)) as int?;
                        Console.WriteLine($" row[{row}]:col[{col}] -> {name} : {value}");
                    }
                    Console.WriteLine();
                }
            }
        }
    }
}
سطر به سطر و ستون به ستون آن‌را به صورت key/value خوانده و نمایش می‌دهد.
این key/valueها هم از نوع object هستند. بنابراین تبدیل آن‌ها و یا اعتبارسنجی مقادیر آن‌ها را به سادگی می‌توانید انجام دهید:
var intValue = Convert.ChangeType(value, typeof(int)) as int?;