مطالب
نوشتن آزمون‌های واحد به کمک کتابخانه‌ی Moq - قسمت سوم - تنظیم مقادیر خواص اشیاء
در قسمت قبل، چون متد Validate سرویس تصدیق هویت استفاده شده، همواره مقدار false را بر می‌گرداند:
_identityVerifier.Initialize();
var isValidIdentity = _identityVerifier.Validate(
     application.Applicant.Name, application.Applicant.Age, application.Applicant.Address);
شیء Mock آن‌را طوری تنظیم کردیم که بر اساس یک applicant مشخص، خروجی true را بازگشت دهد. اما در این بین، کدهای بررسی سرویس creditScorer را کامنت کردیم:
_creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address);
if (_creditScorer.Score < MinimumCreditScore)
{
    return application.IsAccepted;
}
تا آزمایش واحد ما با موفقیت به پایان برسد. در این قسمت، کار تنظیم مقادیر خواص آن‌را در آزمون واحد، به کمک Mocked objects انجام می‌دهیم تا این قسمت از کد نیز پوشش داده شود. برای این منظور به کلاس LoanApplicationProcessor مراجعه کرده و در متد Process آن، ابتدا مجددا از همان overload ساده‌ی فوق متد Validate بجای نمونه‌ی ref دار استفاده کرده و سپس کدهای creditScorer را نیز از حالت کامنت خارج می‌کنیم.


تنظیم مقدار خاصیت Score شیء Mock شده

اینترفیس ICreditScorer به صورت زیر تعریف شده‌است و دارای خاصیت Score می‌باشد که مقدار عددی آن با مقدار حداقل اعتبار تنظیم شده‌ی در کلاس LoanApplicationProcessor مقایسه خواهد شد (MinimumCreditScore = 100_000):
namespace Loans.Services.Contracts
{
    public interface ICreditScorer
    {
        int Score { get; }

        void CalculateScore(string applicantName, string applicantAddress);
    }
}
برای تنظیم مقدار خاصیت Score، در متد Accept آزمون‌های واحد تهیه شده، می‌توان به صورت زیر عمل کرد:
var mockCreditScorer = new Mock<ICreditScorer>();
mockCreditScorer.Setup(x => x.Score).Returns(110_000);
که بسیار شبیه به نحوه‌ی تنظیم مقادیر بازگشتی متدها است. در متد Setup می‌توان به صورت strongly typed به تمام خواص اینترفیس ICreditScorer دسترسی یافت و سپس توسط متد Returns، مقدار بازگشتی آن‌ها را تنظیم نمود.
اکنون اگر متد آزمایش واحد Accept را بررسی کنیم، چون شخص درخواست دهنده، دارای اعتبار بیشتری از حداقل اعتبار مورد نیاز است، این آزمایش با موفقیت به پایان خواهد رسید. اگر این تنظیم صورت نمی‌گرفت، شیء mockCreditScorer، مقدار پیش‌فرض int یا همان صفر را به عنوان مقدار Score بازگشت می‌داد.


تنظیم مقادیر خواص تو در تو و سلسله مراتبی اشیاء Mock شده

برای کار با خواص تو در تو، ابتدا دو مدل زیر را ایجاد می‌کنیم:
namespace Loans.Models
{
    public class ScoreResult
    {
        public ScoreValue ScoreValue { get; }
    }

    public class ScoreValue
    {
        public int Score { get; }
    }
}
اکنون بجای مقدار ساده‌ی int Score { get; }، از نمونه‌ی ScoreResult فوق، در اینترفیس ICreditScorer استفاده خواهیم کرد:
using Loans.Models;

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

        void CalculateScore(string applicantName, string applicantAddress);
        
        ScoreResult ScoreResult { get; }
    }
}
در ادامه برای استفاده‌ی از ScoreResult، به کلاس LoanApplicationProcessor مراجعه کرده و در انتهای متد Process آن، این تغییر را ایجاد می‌کنیم:
//if (_creditScorer.Score < MinimumCreditScore)
if (_creditScorer.ScoreResult.ScoreValue.Score < MinimumCreditScore)
اینبار اگر متد آزمون واحد Accept را اجرا کنیم، با یک null reference exception به پایان می‌رسد؛ چون اولین سطح این شیء تو در تو، یعنی ScoreResult، مساوی نال است.
برای رفع این مشکل در متد آزمون واحد Accept، باید به صورت زیر عمل کرد:
var mockCreditScorer = new Mock<ICreditScorer>();
mockCreditScorer.Setup(x => x.Score).Returns(110_000);

var mockScoreValue = new Mock<ScoreValue>();
mockScoreValue.Setup(x => x.Score).Returns(110_000);

var mockScoreResult = new Mock<ScoreResult>();
mockScoreResult.Setup(x => x.ScoreValue).Returns(mockScoreValue.Object);

mockCreditScorer.Setup(x => x.ScoreResult).Returns(mockScoreResult.Object);
ابتدا از پایین‌ترین سطح یعنی ScoreValue شروع و مقدار خاصیت Score آن‌را تنظیم می‌کنیم.
سپس یک سطح بالاتر را یعنی ScoreResult را تنظیم خواهیم کرد. در اینجا نیاز است خاصیت ScoreValue آن به mock object قبلی تنظیم شود. به همین جهت Returns آن به خاصیت Object شیء mockScoreValue، تنظیم شده‌است.
در آخر برای تنظیم خاصیت ScoreResult شیء mockCreditScorer اصلی، از شیء mockScoreResult استفاده خواهیم کرد.

در این حالت اگر متد آزمون واحد Accept را اجرا کنیم، اینبار به خطای زیر برخواهیم خورد:
Test method Loans.Tests.LoanApplicationProcessorShould.Accept threw exception:
System.NotSupportedException: Unsupported expression: x => x.Score
Non-overridable members (here: ScoreValue.get_Score) may not be used in setup / verification expressions.
عنوان می‌کند که خاصیت Score شیء ScoreValue، قابل بازنویسی نیست (Non-overridable). منظورش این است که برای mocking آن خاصیت، باید آن‌را virtual تعریف کنیم تا کتابخانه‌ی Moq بتواند آن‌را بازنویسی کند. به همین جهت، هر دو خاصیتی را که در اینجا قصد بازنویسی آن‌ها را داریم، به صورت virtual تعریف می‌کنیم:
namespace Loans.Models
{
    public class ScoreResult
    {
        public virtual ScoreValue ScoreValue { get; }
    }

    public class ScoreValue
    {
        public virtual int Score { get; }
    }
}
اکنون اگر متد آزمایش واحد Accept را بررسی کنیم با موفقیت به پایان خواهد رسید.


ساده سازی روش تنظیم مقادیر خواص تو در تو و سلسله مراتبی اشیاء Mock شده

روش دیگری نیز برای تنظیم مقادیر خواص تو در تو در کتابخانه‌ی Moq وجود دارد:
mockCreditScorer.Setup(x => x.ScoreResult.ScoreValue.Score).Returns(110_000);
کتابخانه‌ی Moq قادر است به نحوی که مشاهده می‌کنید، سلسله مراتب اشیاء را به صورت strongly typed ایجاد کرده و در نهایت خاصیت Score آن‌را به 110_000 تنظیم کند.
بدیهی است در این حالت نیز باید شرط virtual بودن این خواص، برقرار باشد؛ در غیراینصورت همان استثنای NotSupportedException را دریافت خواهیم کرد.

یک نکته: اگر در زمان تشکیل یک Mock object، مقدار خاصیت DefaultValue آن‌را به صورت زیر تنظیم کنیم:
var mockCreditScorer = new Mock<ICreditScorer> { DefaultValue = DefaultValue.Mock };
تمام خواص تو در توی موجود در ICreditScorer، به صورت خودکار با نمونه‌های پیش‌فرض آن‌ها مقدار دهی و آماده‌ی استفاده خواهند شد. اگر بجای مقدار DefaultValue.Mock از DefaultValue.Empty استفاده شود، این مقادیر پیش‌فرض، نال خواهد بود (که همان حالت پیش‌فرض new Mock است).


بررسی تغییرات مقادیر خواص اشیاء Mock شده

کتابخانه‌ی Moq، امکان ردیابی تغییرات مقادیر خواص اشیاء Mock شده را نیز داراست. برای نمایش آن، فرض کنید خاصیت جدید Count را به اینترفیس ICreditScorer اضافه کرده‌ایم:
using Loans.Models;

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

        void CalculateScore(string applicantName, string applicantAddress);
        
        ScoreResult ScoreResult { get; }
        
        int Count { get; set; }
    }
}
سپس در کلاس LoanApplicationProcessor و متد Process آن، هربار که CalculateScore فراخوانی می‌شود، یکبار مقدار Count را افزایش می‌دهیم:
_creditScorer.CalculateScore(application.Applicant.Name, application.Applicant.Address);
_creditScorer.Count++;
اکنون در متد آزمون واحد Accept، بررسی می‌کنیم که آیا پس از یکبار فراخوانی متد CalculateScore، مقدار Count برای مثال 1 شده‌است یا خیر؟
Assert.AreEqual(1, mockCreditScorer.Object.Count);
تا اینجا اگر آزمون واحد را اجرا کنیم، با شکست مواجه خواهد شد. چون کتابخانه‌ی Moq تغییرات مقادیر خواص شیء mockCreditScorer.Object را ردیابی نمی‌کند و مقدار mockCreditScorer.Object.Count، همان مقدار پیش‌فرض نوع int، یعنی صفر می‌باشد.
برای فعال سازی ردیابی تغییرات مقادیر خاصیت Count، تنها کافی است آن‌را توسط متد SetupProperty، معرفی کنیم:
mockCreditScorer.SetupProperty(x => x.Count);
پس از این تغییر، بررسی متد آزمون واحد Accept با موفقیت به پایان می‌رسد.

در اینجا می‌توان یک مقدار اولیه را هم درنظر گرفت:
mockCreditScorer.SetupProperty(x => x.Count, 10);
بدیهی است در این صورت Assert.AreEqual ما با شکست مواجه می‌شود؛ چون اینبار مقدار Count نهایی، بر اساس این مقدار اولیه، 11 خواهد بود.


فعالسازی بررسی تغییرات تمام مقادیر خواص اشیاء Mock شده

اگر تعداد خواصی که قرار است مورد ردیابی قرارگیرند زیاد است، بجای فراخوانی متد SetupProperty بر روی تک تک آن‌ها، می‌توان تمام آن‌ها را به صورت زیر تحت کنترل قرار داد:
mockCreditScorer.SetupAllProperties();

نکته‌ی مهم: محل قرارگیری SetupAllProperties مهم است. برای مثال اگر این سطر را پس از سطر تنظیم مقدار پیش‌فرض x.ScoreResult.ScoreValue.Score قرار دهید، آزمایش با شکست مواجه می‌شود؛ چون تنظیمات بازگشت مقادیر پیش‌فرض خواص را به طور کامل بازنویسی می‌کند. بنابراین این سطر باید پیش از سطر تنظیم مقادیر پیش‌فرض خواص Mock شده، فراخوانی شود تا بر روی این مقادیر تنظیمی، تاثیری نداشته باشد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MoqSeries-03.zip
مطالب دوره‌ها
تزریق خودکار وابستگی‌ها در SignalR
فرض کنید لایه سرویس برنامه دارای اینترفیس و کلاس‌های زیر است:
namespace SignalR02.Services
{
    public interface ITestService
    {
        int GetRecordsCount();
    }
}

namespace SignalR02.Services
{
    public class TestService : ITestService
    {
        public int GetRecordsCount()
        {
            return 10; // It's just a sample to test IOC's.
        }
    }
}
قصد داریم از این لایه، توسط تزریق وابستگی‌ها در Hub برنامه استفاده کنیم:
    [HubName("chat")]
    public class ChatHub : Hub
    {
        //جهت آزمایش تزریق خودکار وابستگی‌ها
        private readonly ITestService _testService;
        public ChatHub(ITestService testService)
        {
            _testService = testService;
        }

        public void SendMessage(string message)
        {
            var msg = string.Format("{0}:{1}", Context.ConnectionId, message);
            Clients.All.hello(msg);

            Clients.All.hello(string.Format("RecordsCount: {0}", _testService.GetRecordsCount()));
برنامه، همان برنامه‌ای است که در دوره جاری تکمیل گردیده است. فقط در اینجا سازنده کلاس اضافه شده و سپس اینترفیس ITestService به عنوان پارامتر آن تعریف گردیده است. در ادامه می‌خواهیم کار وهله سازی و تزریق نمونه مرتبط را توسط StructureMap به صورت خودکار انجام دهیم.
برای این منظور یک کلاس جدید را به نام StructureMapDependencyResolver به برنامه اضافه کنید:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.SignalR;
using StructureMap;

namespace SignalR02.Utils
{
    public class StructureMapDependencyResolver : DefaultDependencyResolver
    {
        private readonly IContainer _container;
        public StructureMapDependencyResolver(IContainer container)
        {
            if (container == null)
            {
                throw new ArgumentNullException("container");
            }
            _container = container;
        }
        public override object GetService(Type serviceType)
        {
            return !serviceType.IsAbstract && !serviceType.IsInterface && serviceType.IsClass
                               ? _container.GetInstance(serviceType)
                               : (_container.TryGetInstance(serviceType) ?? base.GetService(serviceType));
        }
        public override IEnumerable<object> GetServices(Type serviceType)
        {
            return _container.GetAllInstances(serviceType).Cast<object>().Concat(base.GetServices(serviceType));
        }
    }
}
کار این کلاس، تعویض DefaultDependencyResolver توکار SignalR با StructureMap است. از این جهت که برای مثال در سراسر برنامه از StructureMap جهت تزریق وابستگی‌ها استفاده شده است و قصد داریم در قسمت Hub آن نیز یکپارچگی کار حفظ گردد.
برای استفاده از این کلاس تعریف شده فقط کافی است Application_Start فایل Global.asax.cs برنامه هاب را به نحو ذیل تغییر دهیم:
using System;
using System.Web;
using System.Web.Routing;
using Microsoft.AspNet.SignalR;
using SignalR02.Services;
using SignalR02.Utils;
using StructureMap;

namespace SignalR02
{
    public class Global : HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            ObjectFactory.Initialize(cfg => 
            {
                cfg.For<IDependencyResolver>().Singleton().Add<StructureMapDependencyResolver>(); 
                // the rest ...
                cfg.For<ITestService>().Use<TestService>();
            });
            GlobalHost.DependencyResolver = ObjectFactory.GetInstance<IDependencyResolver>();

            // Register the default hubs route: ~/signalr
            RouteTable.Routes.MapHubs(new HubConfiguration
            {
                EnableCrossDomain = true
            });            
        }
    }
}
در اینجا در ابتدای کار IDependencyResolver توکار StructureMap با کلاس StructureMapDependencyResolver وهله سازی می‌گردد. سپس تعاریف متداول تنظیمات کلاس‌ها و اینترفیس‌های لایه سرویس برنامه اضافه می‌شوند. همچنین نیاز است GlobalHost.DependencyResolver توکار SignalR نیز به نحوی که ملاحظه می‌کنید مقدار دهی گردد.

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

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

برای شناسایی ایرادات در کد و بهبود کیفیت کدها می‌توانید از ابزارهای دسته‌ی lint استفاده کنید که تعدادی از معروفترین این ابزارها (jslint ,cpplint ,eslint ,nodelint و ...) هستند.
برای نصب چنین ابزاری مثل lint می‌توانید به شکل زیر آن را نصب کنید:
npm install -g eslint
فلگ g در بالا به معنای global بوده و باعث می‌شود که eslint نصب شده، بر روی همه پروژه‌های node.js اجرا شود.
برای اولین بار نیاز است که آماده سازی ابتدایی برای این کتابخانه صورت بگیرد که با دستور زیر قابل انجام است:
eslint --init
سپس تعدادی سوال شخصی از شما پرسیده می‌شود که باید پاسخ داده شود که نمونه‌ای از آن‌را در زیر می‌بینید:
? How would you like to configure ESLint? Answer questions about your style
? Are you using ECMAScript 6 features? Yes
? Are you using ES6 modules? Yes
? Where will your code run? Browser, Node
? Do you use CommonJS? No
? Do you use JSX? No
? What style of indentation do you use? Spaces
? What quotes do you use for strings? Double
? What line endings do you use? Windows
? Do you require semicolons? Yes
? What format do you want your config file to be in? JSON
Successfully created .eslintrc.json file in D:\ali\electron
از این پس با دستور زیر، قادرید کیفیت کدهای خود را بهبود بخشید
eslint .
یا
eslint index.js

می‌توانید این دستور را در در خصوصیت test، در بخش اسکریپت، به شکل زیر وارد کنید:
{
  "name": "electron",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "electron .",
    "test":"eslint ."
  },
  "author": "",
  "license": "ISC"
}
تا از این پس با دستور زیر قابل اجرا باشد:
npm test
بعد از اجرای دستور و طبق سوالات پاسخ داده شده، بهبود کیفیت کد به شکل زیر پاسخ داده می‌شود:
D:\ali\electron\index.js
   1:26  error  Strings must use doublequote                            quotes
  16:8   error  Strings must use doublequote                            quotes
  19:3   error  Expected indentation of 4 space characters but found 2  indent
  22:3   error  Expected indentation of 4 space characters but found 2  indent

✖ 4 problems (4 errors, 0 warnings)

برای نامگذاری فایل‌های js، به جای استفاده از _ از - استفاده کنید. این قاعده‌ای است که گیت هاب در نامگذاری فایل‌های js انجام می‌دهد. یعنی به جای عبارت dotnet_tips.js از عبارت dotnet-tips.js استفاده کنید. خاطرتان جمع باشد که هیچ فایل جاوااسکرپیتی رسمی در الکترون، خارج از این قاعده نامگذاری نشده است.
سعی کنید از جدیدترین قواعد موجود در ES6 استفاده کنید مانند:
let برای تعریف متغیرها
const برای تعریف ثابت ها
توابع جهتی به جای نوشتن عبارت function
استفاده از template string‌ها به جای چسباندن رشته‌ها از طریق عملگر +

برای نامگذاری متغیرها از همان قوانین تعریف شده برای node.js استفاده کنید که شامل موارد زیر است:
- موقعی که یک ماژول را به عنوان یک کلاس استفاده می‌کنید از متد CamelCase استفاده کنید مثل BrowserWindow
- موقعی که از یک ماژول که حاوی مجموعه‌ای از دستورات و api‌ها است استفاده می‌کنید، از قانون mixedCase استفاده کنید مثل app یا globalShortcut
- موقعی که یک api به عنوان خصوصیت یک شیء مورد استفاده قرار میگیرد، از mixedCase استفاده کنید؛ به عنوان مثال win.webContents یا fs.readFileSync
- برای مابقی اشیا مثل دستورات process و یا تگ webview که به شکل متفاوت‌تری مورد استفاده قرار می‌گیرند، از همان عنوان‌های خودشان استفاده می‌کنیم.

موقعی که api جدیدی را می‌سازید و قصد تعریف متدی را دارید بهتر است دو متد برای get و set تعریف شود، نسبت به حالتی که در کتابخانه جی کوئری مورد استفاده قرار می‌گیرد یعنی عبارت‌های زیر
setText('test');
getText();
نسبت به عبارت
.text([text]);
ترجیح داده می‌شوند.
مطالب
تعریف رنگ در iTextSharp

در کتابخانه‌ی iTextSharp به جهت سازگاری با کتابخانه‌ی اصلی، رنگ‌ها را بر اساس کلاسی به نام BaseColor تعریف کرده‌اند؛ که ای‌کاش به جای این‌کار، همه را با کلاس Color فضای نام استاندارد System.Drawing جایگزین می‌کردند. همین مشکل با فونت هم هست. یک کلاس فونت در فضای نام iTextSharp.text وجود دارد به علاوه کلاس فونت تعریف شده در فضای نام استاندارد System.Drawing دات نت؛ که خیلی سریع می‌تواند به خطای کامپایل زیر ختم شود:

'Font' is an ambiguous reference between 'iTextSharp.text.Font' and 'System.Drawing.Font'	


و در نهایت مجبور خواهیم شد که به صورت صریح علام کنیم، iTextSharp.text.Font منظور ما است و نه آن یکی.
در کل اگر با کلاس Color فضای نام استاندارد System.Drawing بیشتر راحت هستید به صورت زیر هم می‌توان رنگ‌های متداول را مورد استفاده قرار داد:

تعریف رنگ‌ها بر اساس نام آن‌ها:

var color = new BaseColor(Color.LightGray);


تعریف رنگ‌ها بر اساس مقادیر Hex متداول در المان‌های HTML :

var color = new BaseColor(ColorTranslator.FromHtml("#1C5E55"));


این نکته‌ای است که شاید خیلی‌ها از وجود آن بی‌اطلاع باشند. به صورت پیش فرض در کلاس استاندارد ColorTranslator، امکان دریافت رنگ‌های بکاررفته در المان‌های HTML به کمک متد ColorTranslator.FromHtml مهیا است.
البته اگر زمانی خواستید خودتان این متد را پیاده سازی کنید، نکته‌ی آن به صورت زیر است:

string htmlColor = "#1C5E55";

int x = Convert.ToInt32(htmlColor.Replace("#", "0x"), 16);
byte red   = (byte)((x & 0xff0000) >> 16);
byte green = (byte)((x & 0xff00) >> 8);
byte blue  = (byte)(x & 0xff);

سپس کلاس‌های Color و همچنین BaseColor امکان پذیرش این اجزای حاصل را دارند (به کمک متد Color.FromRgb و یا سازنده‌ی BaseColor).
علت ذکر ColorTranslator.FromHtml به این بر می‌گردد که ترکیبات رنگ جالبی را می‌توان از جداول HTML ایی موجود در سایت‌های مختلف ایده گرفت و یا حتی از قالب‌های پیش فرض GridView در ASP.NET مثلا.


اکنون برای ساخت جدولی مانند شکل فوق، به ازای هر سلولی که مشاهده می‌کنید باید یکبار BorderColor و BackgroundColor تنظیم شوند. رنگ متن هم از رنگ فونت دریافت می‌شود:

var pdfCell = new PdfPCell(new Phrase(Text, Font))

{
RunDirection = ...,
BorderColor = ...,
BackgroundColor = ...
};


بازخوردهای پروژه‌ها
نمایش گزارش در پنجره جدید
هنگام فراخوانی Generate امکان نمایش گزارش در پنجره جدید وجود دارد؟
.Generate(f => { f.FlushInBrowser(Guid.NewGuid().ToString("D") + ".pdf", FlushType.Inline); });
مطالب
پیشنهاد یک دیکشنری کم دردسرتر!
نگارش ابتدایی «iTextSharp.LGPLv2.Core » بر اساس کدهای اولیه‌ی iTextSharp بود که مستقیما از جاوا به سی‌شارپ ترجمه شده بود. این کدها پر بودند از ساختارهای داده‌ای مانند Hashtable و ArrayList که مرتبط هستند با روزهای آغازین ارائه‌ی دات نت 1؛ پیش از ارائه‌ی Generics. برای مثال نوع Hashtable، همانند ساختار داده‌ی Dictionary عمل می‌کند، اما جنریک نیست؛ یعنی شبیه به <Dictionary<object, object عمل می‌کند و برای کار با آن، باید مدام از تبدیل نوع‌های داده‌ها (یا همان boxing) از نوع object‌، به نوع داده‌ی مدنظر، استفاده کرد که این تبدیل نوع‌ها، همیشه به همراه کاهش کارآیی هم هستند. به علاوه در حین کار با Hashtable، اگر کلیدی در مجموعه‌ی آن وجود نداشته باشد، فقط نال را بازگشت می‌دهد، اما Dictionary، یک استثنای یافت نشدن کلید را صادر می‌کند. بنابراین فرض کنید که با هزاران سطر کد استفاده کننده‌ی از Hashtable طرف هستید که اگر آن‌ها را تبدیل به Dictionary‌های جنریک متناسبی کنید تا کارآیی برنامه بهبود یابد، تمام موارد استفاده‌ی از آن‌ها‌را نیز باید به همراه TryGetValue‌ها کنید تا از شر استثنای یافت نشدن کلید درخواستی، در امان باشید. در این مطلب روش مواجه شدن با یک چنین حالتی را با حداقل تغییر در کدها بررسی خواهیم کرد.


ممنوع کردن استفاده‌ی از ساختارهای داده‌ی غیرجنریک

قدم اول مواجه شدن با یک چنین کدهای قدیمی، ممنوع کردن استفاده‌ی از ساختارهای داده‌ی غیرجنریک و الزام به تبدیل آن‌ها به نوع‌های جدید است. برای این منظور می‌توان از Microsoft.CodeAnalysis.BannedApiAnalyzers استفاده کرد که توضیحات بیشتر آن‌را در مطلب «غنی سازی کامپایلر C# 9.0 با افزونه‌ها» پیشتر بررسی کرده‌ایم. به صورت خلاصه، ابتدا بسته‌ی نیوگت آن‌را به صورت یک آنالایزر جدید به فایل csproj. برنامه معرفی می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk">
    <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>
    </ItemGroup>
    <ItemGroup>
        <AdditionalFiles Include="$(MSBuildThisFileDirectory)BannedSymbols.txt" Link="Properties/BannedSymbols.txt"/>
    </ItemGroup>
</Project>
همچنین در اینجا نیاز است یک فایل متنی BannedSymbols.txt را نیز به آن معرفی کرد؛ برای مثال با این محتوا:
# https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md
T:System.Collections.ICollection;Don't use a non-generic data structure.
T:System.Collections.Hashtable;Don't use a non-generic data structure.
T:System.Collections.ArrayList;Don't use a non-generic data structure.
T:System.Collections.SortedList;Don't use a non-generic data structure.
T:System.Collections.Stack;Don't use a non-generic data structure.
T:System.Collections.Queue;Don't use a non-generic data structure.
این تنظیمات سبب خواهند شد تا اگر در کدهای ما، ساختارهای داده‌ی غیرجنریکی در حال استفاده بودند، با یک اخطار ظاهر شوند و جهت سخت‌گیری بیشتر، روش تبدیل اخطارها به خطاها را نیز در مطلب «غنی سازی کامپایلر C# 9.0 با افزونه‌ها» بررسی کرده‌ایم تا مجبور به اصلاح آن‌ها شویم.


پیشنهاد یک دیکشنری کم دردسرتر!

برای نمونه پس از تنظیمات فوق، مجبور به تغییر تمام hash tableها به دیکشنری‌های جدید جنریک خواهیم شد؛ اما ... اگر اینکار را انجام دهیم، برنامه‌ای که تا پیش از این بدون مشکل کار می‌کرد، اکنون با استثناهای متعدد یافت نشدن کلیدها، خاتمه پیدا می‌کند! چون دیگر دیکشنری‌های جدید، همانند hash tableهای قدیمی، در صورت عدم وجود کلیدی، نال را بازگشت نمی‌دهند.
برای رفع این مشکل و اصلاح انبوهی از کدها با حداقل تغییرات و عدم تکرار TryGetValueها در همه‌جا، می‌توان دسترسی به ایندکس‌های یک دیکشنری استاندارد دات نت را به صورت زیر با ارث‌بری از آن، بازنویسی کرد:
/// <summary>
///     This custom IDictionary doesn't throw a KeyNotFoundException while accessing its value by a given key
/// </summary>
public interface INullValueDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    new TValue this[TKey key] { get; set; }
}

/// <summary>
///     This custom IDictionary doesn't throw a KeyNotFoundException while accessing its value by a given key
/// </summary>
public class NullValueDictionary<TKey, TValue> : Dictionary<TKey, TValue>, INullValueDictionary<TKey, TValue>
{
    public new TValue this[TKey key]
    {
        get => TryGetValue(key, out var val) ? val : default;
        set => base[key] = value;
    }
}
همانطور که مشاهده می‌کنید، اگر بجای Dictionary، از NullValueDictionary پیشنهادی استفاده کنیم، دیگر نیازی نیست تا هزاران TryGetValue را در سراسر کدهای برنامه، تکرار و پراکنده کنیم و با حداقل تغییرات می‌توان معادل بهتری را بجای Hashtable قدیمی داشت.
مطالب
چگونه کد قابل تست بنویسیم - قسمت دوم و پایانی
گام 3 – از بین بردن ارتباط لایه‌ها (Loose Coupling)
بجای استفاده از اشیاء واقعی، براساس interface‌ها برنامه نویسی کنید. اگر شما کد خود را با استفاده از IShoppingCartService  به عنوان یک interface بجای استفاده از شیء واقعی ShoppingCartService نوشته باشید، زمانیکه تست را مینویسید، میتوانید یک سرویس کارت خرید جعلی (mocking) که IShoppingCartService را پیاده سازی کرده جایگزین شیء اصلی نمایید. در کد زیر، توجه کنید تنها تغییر این است که متغیر عضو اکنون از نوع IShoppingCartService  است بجای ShoppingCartService .
public interface IShoppingCartService
{
    ShoppingCart GetContents();
    ShoppingCart AddItemToCart(int itemId, int quantity);
}
public class ShoppingCartService : IShoppingCartService
{
    public ShoppingCart GetContents()
    {
        throw new NotImplementedException("Get cart from Persistence Layer");
    }
    public ShoppingCart AddItemToCart(int itemId, int quantity)
    {
        throw new NotImplementedException("Add Item to cart then return updated cart");
    }
}
public class ShoppingCart
{
    public List<product> Items { get; set; }
}
public class Product
{
    public int ItemId { get; set; }
    public string ItemName { get; set; }
}
public class ShoppingCartController : Controller
{
    //Concrete object below points to actual service
    //private ShoppingCartService _shoppingCartService;
    //loosely coupled code below uses the interface rather than the 
    //concrete object
    private IShoppingCartService _shoppingCartService;
    public ShoppingCartController()
    {
        _shoppingCartService = new ShoppingCartService();
    }
    public ActionResult GetCart()
    {
        //now using the shared instance of the shoppingCartService dependency
        ShoppingCart cart = _shoppingCartService.GetContents();
        return View("Cart", cart);
    }
    public ActionResult AddItemToCart(int itemId, int quantity)
    {
        //now using the shared instance of the shoppingCartService dependency
        ShoppingCart cart = _shoppingCartService.AddItemToCart(itemId, quantity);
        return View("Cart", cart);
    }
}

گام 4 – وابستگی‌ها را تزریق کنید
اکنون ما تمام وابستگی‌ها را در یک جا مرکزیت داده‌ایم و کد ما رابطه کمی با آن وابستگی‌ها دارد. همانند گذشته، چندین راه برای پیاده سازی این گام وجود دارد. بدون استفاده از ابزارهای کمکی برای این مفهوم، ساده‌ترین راه دوباره نویسی (Overload) متد ایجاد کننده است:
//loosely coupled code below uses the interface rather 
//than the concrete object
private IShoppingCartService _shoppingCartService;
//MVC uses this constructor 
public ShoppingCartController()
{
    _shoppingCartService = new ShoppingCartService();
}
//You can use this constructor when testing to inject the    
//ShoppingCartService dependency
public ShoppingCartController(IShoppingCartService shoppingCartService)
{
    _shoppingCartService = shoppingCartService;
}

گام 5 – تست را با استفاده از یک شیء جعلی (Mocking) انجام دهید
مثالی از یک سناریوی تست ممکن در زیر آمده است. توجه کنید که یک شیء جعلی از نوع کلاس ShoppingCartService ساخته‌ایم. این شیء جعلی فرستاده می‌شود به شیء Controller و متد GetContents پیاده سازی میشود تا بجای آنکه کد اصلی را که به منبع داده مراجعه می‌کند اجرا نماید، داده‌های جعلی و شبیه سازی شده را برگرداند. بدلیل آنکه تمام کد را نوشته ایم، بسیار سریعتر از اجرای کوئری بر روی دیتابیس اجرا خواهد شد و دیگر نگرانی بابت تهیه داده تستی و یا تصحیح داده بعد از اتمام تست (بازگرداندن داده به حالت قبل از تست) نخواهم داشت. توجه داشته باشید که بدلیل مرکزیت دادن به وابستگی‌ها در گام 2 ، تنها باید یکبار آنرا تزریق نماییم و بخاطر کاری که در گام 3 انجام شد، وابستگی ما به حدی پایین آمده که میتوانیم هر شیء‌ایی  را  (جعلی و یا حقیقی) ارسال کنیم و فقط کافیست شیء مورد نظر IShoppingCartService را پیاده سازی کرده باشد.
[TestClass]
public class ShoppingCartControllerTests
{
    [TestMethod]
    public void GetCartSmokeTest()
    {
        //arrange
        ShoppingCartController controller = 
           new ShoppingCartController(new ShoppingCartServiceStub());
        // Act
        ActionResult result = controller.GetCart();
        // Assert
        Assert.IsInstanceOfType(result, typeof(ViewResult));
    }
}
/// <summary>
/// This is is a stub of the ShoppingCartService
/// </summary>
public class ShoppingCartServiceStub : IShoppingCartService
{
    public ShoppingCart GetContents()
    {
        return new ShoppingCart
        {
            Items = new List<product> {
                new Product {ItemId = 1, ItemName = "Widget"}
            }
        };
    }
    public ShoppingCart AddItemToCart(int itemId, int quantity)
    {
        throw new NotImplementedException();
    }
}

مطالب تکمیلی
از یک ابزار کنترل وابستگی (IoC/DI) استفاده کنید:
از رایج‌ترین و عمومی‌ترین ابزارهای کنترل وابستگی برای .Net می‌توان به StructureMap و CastleWindsor اشاره کرد. در کد نویسی واقعی، شما وابستگی‌های بسیاری خواهید داشت، که این وابستگی‌ها هم وابستگی هایی دارند که به سرعت از مدیریت شما خارج خواهند شد. راه حل این مشکل استفاده از یک ابزار کنترل وابستگی خواهد بود.
از یک چارچوب تجزیه (Isolation Framework) استفاده نمایید:
برای ایجاد اشیاء جعلی ممکن است کار زیادی لازم باشد و  استفاده از یک Isolation Framework میتواند زمان و میزان کد نویسی شمارا کم کند. از رایج‌ترین این ابزارها میتوان Rhino Mocks  و Moq را نام برد.
مطالب
یکسان سازی "ی" و "ک" دریافتی در حین استفاده از Entity framework

در مورد یکسان سازی ی و ک در حین استفاده از WCF RIA Services پیشتر مطلبی را در این سایت خوانده بودید. جهت تکمیل این بحث، بسط این روش به Entity framework به صورت زیر خواهد بود:

using System.Data;
using System.Data.Objects;
using System.Linq;
using System.Reflection;

namespace EfExt
{
public static class CorrectYeKe
{
public static void ApplyCorrectYeKe(this ObjectContext ctx)
{
if (ctx == null)
return;

//پیدا کردن موجودیت‌های تغییر کرده
var changedEntities = ctx.ObjectStateManager.GetObjectStateEntries(
EntityState.Added | EntityState.Modified
);

foreach (var entity in changedEntities)
{
if (entity.Entity == null) continue;

//یافتن خواص قابل تنظیم و رشته‌ای این موجودیت‌ها
var propertyInfos = entity.Entity.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance
).Where(p => p.CanRead && p.CanWrite && p.PropertyType == typeof(string));

var pr = new PropertyReflector();

//اعمال یکپارچگی نهایی
foreach (var propertyInfo in propertyInfos)
{
var propName = propertyInfo.Name;
var val = pr.GetValue(entity.Entity, propName);
if (val != null)
{
pr.SetValue(
entity.Entity,
propName,
val.ToString().ApplyUnifiedYeKe());
}
}
}
}
}
}

ابتدا موجودیت‌های تغییر کرده یافت خواهند شد (اگر از self tracking entities استفاده می‌کنید استفاده از Context.DetectChanges پیش از فراخوانی این متد ضروری خواهد بود)، سپس در این لیست در مورد تک تک اشیاء، خواص رشته‌ای که readonly نیستند یافت شده و ی و ک آن‌ها یک دست می‌شوند.
محل اعمال آن هم باید پیش از فراخوانی Context.SaveChanges باشد.

سورس این کتابخانه را از اینجا می‌توانید دریافت کنید.

نظرات مطالب
C# 12.0 - Collection Expressions & Spread Operator
اصلاحیه: کارآیی spread operator بیشتر نیست!

در متن در مورد spread operator عنوان شده «که ... نگارش C# 12 آن کارآیی بیشتری دارد». این مورد بدون توجه به #Low-Level C تولیدی، نوشته شد و ... متاسفانه نادرست است!
برای مثال فرض کنید، چنین متدی را دارید که با استفاده از spread operator، کار بازکردن یک آرایه را انجام می‌دهد:
public int[] WithSpread()
{
   int[] data = new int[10_000];
   int[] results = [..data];
   return results;
}
معادل #Low-Level C آن (کد نهایی که کامپایلر برای تبدیل آن به IL تولید می‌کند) به صورت زیر است ( #Low-Level C را در Rider، در منوی #Tools -> IL Viewer -> Select Low-Level C می‌توانید تولید کنید):
public int[] WithSpread()
{
  int[] numArray1 = new int[10000];
  int index1 = 0;
  int[] numArray2 = new int[numArray1.Length];
  int[] numArray3 = numArray1;
  for (int index2 = 0; index2 < numArray3.Length; ++index2)
  {
    int num = numArray3[index2];
    numArray2[index1] = num;
    ++index1;
  }
  return numArray2;
}
همانطور که مشاهده می‌کنید، این قطعه کد در C#12 و دات‌نت 8، به شدت ابتدایی تولید شده و به همراه هیچ نوع بهینه سازی نیست. کارآیی این قطعه کد، نسبت به زمانیکه از متد قدیمی CopyTo آرایه‌ها استفاده می‌شود، به مراتب کمتر است (تا 3 برابر!)؛ چون متد CopyTo به همراه بهینه سازی‌های سخت‌افزاری هم هست. به نظر قرار شده بهینه سازی کارآیی spread operator در نگارش بعدی دات‌نت انجام شود.

برای آزمایش شخصی آن، از کلاس زیر استفاده کنید:
using BenchmarkDotNet.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SpreadBenchmark
{
    [MemoryDiagnoser]
    public class Tests
    {
        private readonly int[] _myData = new int[10_000];

        [Benchmark(Baseline = true)]
        public int[] WithToArray()
        {
            int[] results = _myData.ToArray();
            return results;
        }

        [Benchmark]
        public int[] WithCopyTo()
        {
            int[] results = new int[_myData.Length];
            _myData.CopyTo(results, 0);
            return results;
        }

        [Benchmark]
        public int[] WithSpread()
        {
            int[] results = [.._myData];
            return results;
        }
    }
}
که در فایل Program.cs به این صورت فراخوانی می‌شود:
using BenchmarkDotNet.Running;
using SpreadBenchmark;

BenchmarkRunner.Run<Tests>();
با این وابستگی:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" Version="0.13.10" />
  </ItemGroup>
</Project>
مطالب
آشنایی با Refactoring - قسمت 8

یکی از اشتباهاتی که همه‌ی ما کم و بیش به آن دچار هستیم ایجاد کلاس‌هایی هستند که «زیاد می‌دانند». اصطلاحا به آن‌ها God Classes هم می‌گویند و برای نمونه، پسوند یا پیشوند Util دارند. این نوع کلاس‌ها اصل SRP را زیر سؤال می‌برند (Single responsibility principle). برای مثال یک فایل ایجاد می‌شود و داخل آن از انواع و اقسام متدهای «کمکی» کار با دیتابیس تا رسم نمودار تا تبدیل تاریخ میلادی به شمسی و ... در طی بیش از 10 هزار سطر قرار می‌گیرند. یا برای مثال گروه بندی‌های خاصی را در این یک فایل از طریق کامنت‌های نوشته شده برای قسمت‌های مختلف می‌توان یافت. Refactoring مرتبط با این نوع کلاس‌هایی که «زیاد می‌دانند»، تجزیه آن‌ها به کلاس‌های کوچکتر، با تعداد وظیفه‌ی کمتر است.
به عنوان نمونه کلاس CustomerService زیر، دو گروه کار متفاوت را انجام می‌دهد. ثبت و بازیابی اطلاعات ثبت نام یک مشتری و همچنین محاسبات مرتبط با سفارشات مشتری‌ها:

using System;
using System.Collections.Generic;

namespace Refactoring.Day8.RemoveGodClasses.Before
{
public class CustomerService
{
public decimal CalculateOrderDiscount(IEnumerable<string> products, string customer)
{
// do work
throw new NotImplementedException();
}

public bool CustomerIsValid(string customer, int order)
{
// do work
throw new NotImplementedException();
}

public IEnumerable<string> GatherOrderErrors(IEnumerable<string> products, string customer)
{
// do work
throw new NotImplementedException();
}

public void Register(string customer)
{
// do work
}

public void ForgotPassword(string customer)
{
// do work
}
}
}

بهتر است این دو گروه، به دو کلاس مجزا بر اساس وظایفی که دارند، تجزیه شوند. به این ترتیب نگهداری این نوع کلاس‌های کوچکتر در طول زمان ساده‌تر خواهند شد:

using System;
using System.Collections.Generic;

namespace Refactoring.Day8.RemoveGodClasses.After
{
public class CustomerOrderService
{
public decimal CalculateOrderDiscount(IEnumerable<string> products, string customer)
{
// do work
throw new NotImplementedException();
}

public bool CustomerIsValid(string customer, int order)
{
// do work
throw new NotImplementedException();
}

public IEnumerable<string> GatherOrderErrors(IEnumerable<string> products, string customer)
{
// do work
throw new NotImplementedException();
}
}
}

namespace Refactoring.Day8.RemoveGodClasses.After
{
public class CustomerRegistrationService
{
public void Register(string customer)
{
// do work
}

public void ForgotPassword(string customer)
{
// do work
}
}
}