مطالب
توضیح مثالی از SIMD برای نشان دادن عملکرد آن - SIMD Performance
پیشنیازها

SIMD یا ترجمه آن به فارسی به معنی «تک دستورالعمل و چند داده»، قابلیت آن‌را دارد تا بر روی مقادیر عددی به صورت موازی و با استفاده از پردازنده کار کند. اگر بتوانیم ساختار پروژه‌های خود را به طوری ایجاد کنیم تا بتوانیم از SIMD در پردازش‌های خود استفاده کنیم، سرعت انجام فعالیت‌ها، بسیار زیاد افزایش پیدا خواهند کرد؛ به خصوص این امر در حجم‌های پردازشی زیاد محسوس خواهد بود. البته مدیریت استفاده از منابع و پردازنده نباید فراموش شوند.
اطلاعات لازم از SIMD و نحوه عملکرد آن را می‌توانید در مقاله پیشنیاز بیابید. در این مقاله قصد داریم تا یک مثال ساده از کارآیی SIMD را مطرح کنیم. مثال زیر از مثال SimdSpike الگو برداری شده است و تغییراتی نیز جهت تکمیل شدن آن انجام شده است.
در این مثال می‌خواهیم نمونه کدهایی را با روش‌های معمول اجرا کنیم و زمان اجرای آن را با زمان اجرای همان مثال‌ها با روش SIMD، مقایسه کنیم. 
با استفاده از ویژوال استودیو 2015 آپدیت 3 یک پروژه کنسول با چارچوب دات نت 4.6.1 ایجاد کرده‌ایم. البته می‌توانید ازدیگر نسخه‌ها هم استفاده کنید به شرط آنکه دات نت 4.6x را نصب کرده باشید.

در صورتی که ویژوال استودیوی شما دارای این ورژن و آپدیت نبود، می‌توانید چارچوب دات نت 4.6.1 را جداگانه در سیستم خود نصب نمایید. توجه داشته باشید که برای استفاده از چارچوب دات نت در ویژوال استودیو باید نسخه‌های DevPack یا DeveloperPack را نصب نمایید (دریافت  دات نت 4.6.1 نسخه مخصوص استفاده در ویژال استودیو). 

در پروژه ایجاد شده فایلی به نام Program.cs و در آن کلاس Program وجود دارد. در این کلاس تابع شروع کننده برنامه یعنی Main وجود دارد و برنامه از این تابع شروع خواهد شد.

نمایی از فایل‌های پروژه

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

پس از بررسی وضعیت پشتیبانی از SIMD ، تابعی را که در فایل Utilities.cs نوشته شده است، فراخوانی می‌کنیم. این تابع به بررسی وضعیت تعداد رجیسترهای SIMD و وضعیت انواع نوع‌های داده‌ای در SIMD می‌پردازد. اگر هر نوع داده‌ای از SIMD پشتیبانی کند (که بستگی به نوع پردازنده شما دارد) اندازه هر نوع داده‌ای را در SIMD چاپ می‌کند و در صورت عدم پشتیبانی هر نوع داده‌ای از SIMD مقدار «عدم پشتیبانی SIMD از آن نوع داده‌ای» چاپ خواهد شد. 

  تا به اینجای برنامه کد‌های تابع شروع کننده به صورت زیر خواهد بود. 
using System.Numerics;
using static System.Console;

namespace TestSIMD
{
    class Program
    {
        private const int ArraySize = 7680 * 4320;
        static void Main(string[] args)
        {
            // بررسی وضعیت پشتیبانی از SIMD
            if (!Vector.IsHardwareAccelerated)
            {
                WriteLine("Hardware acceleration not supported.");
                WriteLine();
                return; // عدم پشتیبانی و خاتمه برنامه
            }
            WriteLine("Hardware acceleration is supported"); // اعلام پشتیبانی از SIMD
            WriteLine();

            // بررسی وضعیت نوع‌های داده ای در مشخصات سخت افزاری SIMD
            Utilities.PrintHardwareSpecificSimdEffectiveness();

            //به منظور عدم خروج از برنامه و دیدن نتایج آزمایش
            WriteLine("Press any key to exit");
            ReadKey();
        }
    }
}
اجرای برنامه هم به صورت زیر به نمایش در خواهد آمد. 

در فایل Utilities.cs، توابع دیگری هم وجود دارند که کارآیی هر یک به صورت توضیح در بالای هر تابع نوشته شده است. این توابع برای تولید یک نوع داده‌ای تصادفی و ایجاد آرایه‌ای از نوع داده‌ای به صورت تصادفی به کار برده می‌شوند. می توانید در سورس برنامه این توضیحات را مشاهده کنید.
تا به اینجا تنها به بررسی پشتیبانی سخت افزاری از SIMD پرداختیم و همچنین توانستیم نوع‌های داده‌ای را که SIMD در سخت افزار ما پشتیبانی می‌کند، شناسایی کنیم و اندازه رجیستر‌های آنها را بیابیم.
حال به بررسی عملکرد توابع SIMD می‌پردازیم و با نوشتن چند تابع، زمان اجرای محاسباتی آنها را با نوشتن همان توابع در حالت معمولی و ساده مقایسه می‌کنیم.
 برای انجام مقایسه، زمان اجرای یک عملیات را در حالت معمول، با زمان اجرای همان عملیات در حالت SIMD بررسی می‌کنیم. هر عملیات را 3 مرتبه پشت سر هم اجرا می‌کنیم و زمان آنها را ثبت می‌کنیم تا تفاوت زمان اجرا را با تکرار عملیات نیز مشاهده کنیم. توابعی که آزمایشات را انجام می‌دهند و زمان اجرا را ثبت و نمایش می‌دهند، در فایل PerformanceTests.cs و در کلاس PerformanceTests قرار دارند و از توابع سه کلاس دیگر که عملیات در آن نوشته شده‌اند، استفاده می‌کنند.
  • فایل IntSimdProcessor.cs
    • در این فایل کلاسی به نام IntSimdProcessor قرار دارد که شامل 6 تابع می‌باشد و این تابع‌ها با نوع داده‌ای صحیح یا همان Integer کار می‌کنند. نام کلاس هم به همین خاطر نام گذاری شده است. 
    • این 6 تابع در کل 3 عملیات را شامل عملیات‌های زیر انجام می‌دهند. یکبار در حالت معمولی و یکبار با استفاده از توابع SIMD این کار را انجام می‌دهند:
      • پیدا کردن بزرگترین و کوچکترین عدد در آرایه
      • جمع عناصر دو آرایه با هم با استفاده از یک آرایه کمکی که نتیجه در آرایه کمکی ریخته می‌شود
      • جمع عناصر دو آرایه بدون استفاده از آرایه کمکی که مجموع در آرایه اول ریخته می‌شود
    • در بالای هر تابع در این فایل توضیحات لازم درباره‌ی فعالیت آن تابع ذکر شده است.
 
  • فایل FloatSimdProcessor.cs
    • در این فایل کلاسی با نام FloatSimdProcessor قرار دارد که همانطور که از نام کلاس پیداست، توابعی برای کار بر روی اعداد از نوع داده‌ای float در آن نوشته شده‌اند.
    • در این کلاس هم 6 تابع برای انجام 3 عملیات زیر نوشته شده است که به ازای هر عملیات دو تابع یکی در حالت معمولی و یکی در حالت SIMD نوشته شده است.
      • جمع دو آرایه با استفاده از یک آرایه کمکی - مجموع در آرایه کمکی ریخته می‌شود
      • جمع دو آرایه اول ورودی - مجموع در آرایه سوم ریخته می‌شود
      • جمع دو آرایه بدون استفاده از آرایه کمکی - مجموع در آرایه اول ریخته می‌شود
    • در آزمایشات نوشته شده در کلاس PerformanceTests  تنها از عملیات آخری استفاده شده است و از دو عملیات اول استفاده نشده است که در صورت تمایل می‌توانید از دیگر عملیات‌ها نیز استفاده کنید.
    • در بالای هر تابع در این فایل توضیحات لازم درباره‌ی فعالیت آن تابع نیز ذکر شده است.
 
  • فایل UShortSimdProcessor.cs
    • در این فایل کلاسی با نام UShortSimdProcessor قرار دارد و همانطور که از نام کلاس پیداست، توابعی برای کار بر روی اعداد از نوع داده‌ای ushort یا همان اعداد صحیح کوچک بدون علامت نوشته شده‌اند.
    • در این کلاس 12 تابع برای انجام 6 عملیات زیر نوشته شده‌است که به ازای هر عملیات، دو تابع یکی در حالت معمولی و یکی در حالت SIMD نوشته شده است.
      • جمع دو آرایه اول ورودی که مجموع در آرایه سوم ریخته می‌شود
      • جمع دو آرایه بدون استفاده از آرایه کمکی که مجموع در آرایه اول ریخته می‌شود
      • بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح کوچک بدون علامت
      • جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی
      • جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی بدون بررسی سرریز (Overflow)
      • محاسبه میانگین و بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح کوچک بدون علامت
    • در بالای هر تابع در این فایل توضیحات لازم درباره‌ی فعالیت آن تابع ذکر شده است.
 
حال در کلاس PerformanceTests برای انجام آزمایشات و مقایسه زمان اجرا، 10 تابع وجود دارند که 10 عملیات مختلف را بر روی 3 نوع داده‌ای، اجرا می‌کنند. 3 عملیات از کلاس IntSimdProcessor و یک عملیات از کلاس FloatSimdProcessor و 6 عملیات از کلاس UShortSimdProcessor را مورد آزمایش قرار داده‌ایم که در مجموع شامل 10 آزمایش در 10 تابع مختلف شده است.
public static void TestIntArrayAdditionFunctions(int testSetSize) {
    WriteLine();
    Write("Testing int array addition, generating test data...");
    var intsOne = GetRandomIntArray(testSetSize); //تولید آرایه عددی به صورت تصادفی
    var intsTwo = GetRandomIntArray(testSetSize);
    WriteLine($" done, testing...");// پایان تولید آرایه‌ها و شروع پردازش
    var naiveTimesMs = new List<long>(); // تعریف لیستی برای ریختن زمان پاسخ دهی در حالت ساده و معمولی
    var hwTimesMs = new List<long>(); // تعریف لیستی برای ریختن زمان پاسخ دهی در حالت SIMD و سخت افزاری 
    for (var i = 0; i < 3; i++) { // ایجاد حلقه برای تکرار محاسبات برای اندازه گیری زمان در حالت تکراری
        stopwatch.Restart();//شروع ثبت زمان
        var result = IntSimdProcessor.NaiveSumFunc(intsOne, intsTwo);//اجرای تابع جمع دو آرایه
        var naiveTimeMs = stopwatch.ElapsedMilliseconds;//ثبت زمان
        naiveTimesMs.Add(naiveTimeMs);//افزودن زمان ثبت شده به لیست زمان‌های ساده و معمول
        WriteLine($"Naive analysis took:                {naiveTimeMs}ms (last value = {result.Last()}).");

        stopwatch.Restart();//شروع ثبت زمان
        result = IntSimdProcessor.HWAcceleratedSumFunc(intsOne, intsTwo);//اجرای تابع جمع دو آرایه در حالت سخت افزاری
        var hwTimeMs = stopwatch.ElapsedMilliseconds;//ثبت زمان
        hwTimesMs.Add(hwTimeMs);//افزودن زمان به لیست زمان‌های سخت افزاری
        WriteLine($"Hareware accelerated analysis took: {hwTimeMs}ms (last value = {result.Last()}).");
    }//پایان حلقه و چاپ نتایج
    WriteLine("Int array addition:");
    WriteLine($"Naive method average time:          {naiveTimesMs.Average():.##}");
    WriteLine($"HW accelerated method average time: {hwTimesMs.Average():.##}");
    WriteLine($"Hardware speedup:                   {naiveTimesMs.Average() / hwTimesMs.Average():P}%");
}
در بالا تکه کدی مربوط به تابع آزمایش اول از کلاس PerformanceTests قرار دارد و وظیفه دارد عملیات جمع دو آرایه را با استفاده از یک آرایه کمکی اعداد صحیح، هم در حالت معمولی و هم در حالت SIMD انجام دهد و زمان اجرای آنها را ثبت و نمایش دهد تا بتوانیم این زمان اجرا‌ها را با هم مقایسه کنیم.
ساختار و روند اجرای کلیه آزمایش‌ها و توابع در کلاس PerformanceTests با یکدیگر یکسان است و از یک stopwatch یا همان کرنومتر برای محاسبه زمان اجرا استفاده شده است.
هر کدام از این توابع یک عملیات را مورد بررسی قرار می‌دهند و هر عملیات را 3 مرتبه اجرا می‌کنند تا زمان تکرار اجرا نیز مورد مقایسه قرار گیرد.

نام تابع ذکر شده نشان دهنده آزمایش بر روی آرایه اعداد صحیح یا همان Integer می‌باشد که شامل یک پارامتر ورودی از نوع عدد صحیح می‌باشد. این پارامتر ورودی نشان دهنده اندازه هر آرایه‌ای می‌باشد که قرار است تولید شود.  

TestIntArrayAdditionFunctions(int testSetSize)

در قدم اول این تابع، باید آرایه‌ها را تولید کنیم که کد آن به صورت زیر است.

Write("Testing int array addition, generating test data...");
var intsOne = GetRandomIntArray(testSetSize);
var intsTwo = GetRandomIntArray(testSetSize);
WriteLine($" done, testing...");

ابتدا در خروجی چاپ می‌کنیم که در حال ایجاد داده‌های مربوط به آزمایش هستیم و سپس با استفاده از تابع GetRandomIntArray آرایه‌ای را ایجاد می‌کنیم و در متغیر‌های مربوطه می‌ریزیم. این تابع دارای یک پارامتر ورودی از نوع عدد صحیح است که آرایه‌ای را به طول پارامتر ورودی تولید می‌کند. این تابع در فایل Utilities.cs قرار دارد.

در پایان تولید آرایه‌ها، اتمام تولید و ایجاد آرایه‌ها را با چاپ در خروجی اعلام میکنیم.

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

var naiveTimesMs = new List<long>();
var hwTimesMs = new List<long>();

سپس با ایجاد حلقه ای از 0 تا 3 که در کل 3 مرتبه اجرا می‌شود عملیات را تکرار و زمان آن را ثبت می‌کنیم. 

for (var i = 0; i < 3; i++)

درون حلقه یک عملیات را در دوحالت معمولی یا ساده و SIMD اجرا می‌کنیم. قبل از اجرای عملیات اول ابتدا stopwatch را ریست می‌کنیم. با این کار زمان صفر شده و شروع به اندازه گیری می‌کند. سپس عملیات مربوط به جمع دو آرایه را در حالت معمولی که در فایل IntSimdProcessor.cs قرار دارد، فراخوانی می‌کنیم. پس از اجرای این عملیات مقدار stopwatch را به میلی ثانیه در یک متغیر ذخیره میکنیم و این مقدار را به لیست زمان‌های اجرای معمولی اضافه می‌کنیم. در نهایت نتیجه زمان اجرا را در خروجی چاپ می‌کنیم. 

stopwatch.Restart();
var result = IntSimdProcessor.NaiveSumFunc(intsOne, intsTwo);
var naiveTimeMs = stopwatch.ElapsedMilliseconds;
naiveTimesMs.Add(naiveTimeMs);
WriteLine($"Naive analysis took:                {naiveTimeMs}ms (last value = {result.Last()}).");

پس از اجرای عملیات در حالت ساده یا معمولی، حال نوبت همان عملیات در حالت SIMD می‌باشد. دوباره stopwatch را ریست می‌کنیم و عملیات در SIMD را اجرا کرده و بعد از آن مقدار stopwatch را درون متغیری میریزیم و آن را به لیست زمان‌های اجرای عملیات در SIMD اضافه می‌کنیم و در نهایت نتیجه زمان اجرا را در خروجی چاپ می‌کنیم. 

stopwatch.Restart();
result = IntSimdProcessor.HWAcceleratedSumFunc(intsOne, intsTwo);
var hwTimeMs = stopwatch.ElapsedMilliseconds;
hwTimesMs.Add(hwTimeMs);
WriteLine($"Hareware accelerated analysis took: {hwTimeMs}ms (last value = {result.Last()}).");

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

WriteLine($"Naive method average time:          {naiveTimesMs.Average():.##}");
WriteLine($"HW accelerated method average time: {hwTimesMs.Average():.##}");
WriteLine($"Hardware speedup:                   {naiveTimesMs.Average() / hwTimesMs.Average():P}%");

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

برای اجرای تمامی آزمایش‌ها، کلیه توابع نوشته شده در کلاس PerformanceTests را در کلاس Program و در تابع Main که تابع شروع کننده برنامه می‌باشد، پس از بررسی وضعیت نوع‌های داده‌ای قرار می‌دهیم.

تصویر مربوط به اجرای کامل برنامه را می‌توانید مشاهده می‌کنید. 

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

زمان‌ها در جدول به میلی ثانیه می‌باشد.

ردیف

عملیات

دور اول

دور دوم

دور سوم

میانگین حالت ساده

میانگین حالت SIMD

درحالت ساده

درحالت SIMD

درحالت ساده

درحالت SIMD

درحالت ساده

درحالت SIMD

1

جمع دو آرایه با استفاده از یک آرایه کمکی در اعداد صحیح

157

131

128

131

128

138

137.67

133.33

2

جمع دو آرایه بدون استفاده از آرایه کمکی در اعداد float

122

133

99

99

99

93

106.67

108.33

3

جمع دو آرایه بدون استفاده از آرایه کمکی در اعداد صحیح

83

73

86

88

78

81

82.33

80.67

4

جمع دو آرایه اول ورودی - مجموع در آرایه سوم ریخته می‌شود - در اعداد صحیح کوچک بدون علامت

58

63

50

48

58

46

55.33

52.33

5

جمع دو آرایه بدون استفاده از آرایه کمکی در اعداد صحیح کوچک بدون علامت

55

40

53

36

53

46

53.67

40.67

6

بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح

91

36

91

39

90.67

38

90.66

38

7

بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح کوچک بدون علامت

90

20

89

19

88

18

89

19

8

جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی

33

309

32

263

31

291

32

287.67

9

جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی بدون بررسی سرریز

30

13

29

13

30

12

29.67

12.67

10

محاسبه میانگین و بدست آوردن کمترین و بیشترین مقدار در آرایه اعداد صحیح کوچک بدون علامت

89

50

90

51

90

49

89.57

50



سورس کامل برنامه را که شامل تغییراتی در توابع برای بهبود و اضافه شدن کامنت برای فهم بیشتر کدها می‌باشد، در زیر می‌توانید دریافت کنید: 
   TestSIMD.zip  

مطالب
بررسی ORM های مناسب جهت استفاده در اندروید
با آمدن ORM‌ها به دنیای برنامه نویسی، کار برنامه نویسی نسبت به قبل ساده‌تر و راحت‌تر شد. عدم استفاده کوئری‌های دستی، پشتیبانی از چند دیتابیس و از همه مهمتر و اصلی‌ترین هدف این ابزار "تنها درگیری با اشیا و مدل شیء گرایی" کار را پیش از پیش آسان‌تر نمود.
در این بین به راحتی می‌توان چندین نمونه از این ORM‌ها را  نام برد مثل IBatis , Hibernate ,Nhibernate و EF که از معروفترین آن‌ها هستند.
من در حال حاضر قصد شروع یک پروژه اندرویدی را دارم و دوست دارم بجای استفاده‌ی از Sqlitehelper، از یک ORM مناسب بهره ببرم که چند سوال برای من پیش می‌آید. آیا ORM ای برای آن تهیه شده است؟ اگر آری چندتا و کدامیک از آن‌ها بهتر هستند؟ شاید در اولین مورد کتابخانه‌ی Hibernate جاوا را نام ببرید؛ ولی توجه به این نکته ضروری است که ما در مورد پلتفرم موبایل و محدودیت‌های آن صحبت می‌کنیم. یک کتابخانه همانند Hibernate مطمئنا برای یک برنامه اندروید چه از نظر حجم نهایی برنامه و چه از نظر حجم بزرگش در اجرا، مشکل زا خواهد بود و وجود وابستگی‌های متعدد و دارا بودن بسیاری از قابلیت‌هایی که اصلا در بانک‌های اطلاعاتی موبایل قابل اجرا نیست، باعث می‌شود این فریمورک انتخاب خوبی برای یک برنامه اندروید نباشد.

معیارهای انتخاب یک فریم ورک مناسب برای موبایل:
  • سبک بودن: مهمترین مورد سبک بودن آن است؛ چه از لحاظ اجرای برنامه و چه از لحاظ حجم نهایی برنامه
  • سریع بودن: مطمئنا ORM‌های طراحی شده‌ی موجود، از سرعت خیلی بدی برخوردار نخواهند بود؛ اگر سر زبان هم افتاده باشند. ولی باز هم انتخاب سریع بودن یک ORM، مورد علاقه‌ی بسیاری از ماهاست.
  • یادگیری آسان و کانفیگ راحت تر.

OrmLight

این فریمورک مختص اندروید طراحی نشده ولی سبک بودن آن موجب شده‌است که بسیاری از برنامه نویسان از آن در برنامه‌های اندرویدی استفاده کنند. این فریم ورک جهت اتصالات JDBC و Spring و اندروید طراحی شده است.

نحوه معرفی جداول در این فریمورک به صورت زیر است:
@DatabaseTable(tableName = "users")
public class User {
    @DatabaseField(id = true)
    private String username;
    @DatabaseField
    private String password;
 
    public User() {
        // ORMLite needs a no-arg constructor
    }
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
 
    // Implementing getter and setter methods
    public String getUserame() {
        return this.username;
    }
    public void setName(String username) {
        this.username = username;
    }
    public String getPassword() {
        return this.password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}
با استفاده از کلمات کلیدی DatabaseTable@ در بالای کلاس و DatabaseField@ در بالای هر پراپرتی به معرفی جدول و فیلدهای جدول می‌پردازیم.
سورس این فریمورک را می‌توان در گیت هاب یافت و مستندات آن در این آدرس قرار دارند.


SugarORM
این فریمورک مختص اندروید طراحی شده است. یادگیری آن بسیار آسان است و به راحتی به یاد می‌ماند. همچنین جداول مورد نیاز را به طور خودکار خواهد ساخت. روابط یک به یک و یک به چند را پشتیبانی می‌کند و عملیات CURD را با سه متد Save,Delete و Find که البته FindById هم جزء آن است، پیاده سازی می‌کند.

برای استفاده از این فریمورک نیاز است ابتدا متادیتا‌های زیر را به فایل manifest اضافه کنید:
<meta-data android:name="DATABASE" android:value="my_database.db" />
<meta-data android:name="VERSION" android:value="1" />
<meta-data android:name="QUERY_LOG" android:value="true" />
<meta-data android:name="DOMAIN_PACKAGE_NAME" android:value="com.my-domain" />
برای تبدیل یک کلاس به جدول هم از کلاس این فریم ورک ارث بری می‌کنیم:
public class User extends SugarRecord<User> {
    String username;
    String password;
    int age;
    @Ignore
    String bio; //this will be ignored by SugarORM
 
    public User() { }
 
    public User(String username, String password,int age){
        this.username = username;
        this.password = password;
        this.age = age;
    }
}
بر خلاف OrmLight که باید فیلد جدول را معرفی می‌کردید، اینجا تمام پراپرتی‌ها به اسم فیلد شناخته می‌شوند؛ مگر اینکه در بالای آن از عبارت Ignore@ استفاده کنید.

باقی عملیات آن از قبیل اضافه کردن یک رکورد جدید یا حذف رکورد(ها) به صورت زیر است:
User johndoe = new User(getContext(),"john.doe","secret",19);
johndoe.save(); //ذخیره کاربر جدید در دیتابیس


//حذف تمامی کاربرانی که سنشان 19 سال است
List<User> nineteens = User.find(User.class,"age = ?",new int[]{19});
foreach(user in nineteens) {
    user.delete();
}
برای اطلاعات بیشتر به مستندات آن رجوع کنید.


GreenDAO
موقعیکه بحث کارآیی و سرعت پیش می‌آید نام GreenDAO هست که می‌درخشد. طبق گفته‌ی سایت رسمی آن این فریمورک میتواند در ثانیه چند هزار موجودیت را اضافه و به روزرسانی و بارگیری نماید. این لیست حاوی برنامه‌هایی است که از این فریمورک استفاده می‌کنند. جدول زیر مقایسه‌ای است بین این کتابخانه و OrmLight که نشان میدهد 4.5 برابر سریعتر از OrmLight عمل می‌کند.

غیر از این‌ها در زمینه‌ی حجم هم حرف‌هایی برای گفتن دارد. حجم این کتابخانه کمتر از 100 کیلوبایت است که در اندازه‌ی APK اثر چندانی نخواهد داشت.

آموزش راه اندازی  آن در اندروید استادیو، سورس آن در گیت هاب و مستندات رسمی آن.


Active Android
این کتابخانه از دو طریق فایل JAR و به شیوه maven قابل استفاده است که می‌توانید روش استفاده‌ی از آن را در این لینک ببینید و سورس اصلی آن هم در این آدرس قرار دارد. بعد از اینکه کتابخانه را به پروژه اضافه کردید، دو متادیتای زیر را که به ترتیب نام دیتابیس و ورژن آن هستند، به manifest اضافه کنید:
<meta-data android:name="AA_DB_NAME" android:value="my_database.db" />
<meta-data android:name="AA_DB_VERSION" android:value="1" />
بعد از آن  عبارت ;()ActiveAndroid.Initialize را در اکتیویتی‌های مدنظر اعمال کنید:
public class MyActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActiveAndroid.initialize(this);
 
        //ادامه برنامه
    }
}
برای معرفی کلاس‌ها به جدول هم از دو اعلان Table و Column مانند کد زیر به ترتیب برای معرفی جدول و فیلد استفاده می‌کنیم.
@Table(name = "User")
public class User extends Model {
    @Column(name = "username")
    public String username;
 
    @Column(name = "password")
    public String password;
 
    public User() {
        super();
    }
 
    public User(String username,String password) {
        super();
        this.username = username;
        this.password = password;
    }
}
جهت اطلاعات بیشتر در مورد این کتابخانه به مستندات آن رجوع کنید.


ORMDroid
 از آن دست کتابخانه‌هایی است که سادگی و کم حجم بودن شعار آنان است و سعی دارند تا حد ممکن همه چیز را خودکار کرده و کمترین کانفیگ را نیاز داشته باشد. حجم فعلی آن حدود 20 کیلوبایت بوده و نمی‌خواهند از 30 کیلوبایت تجاوز کند.

برای استفاده‌ی از آن ابتدا دو خط زیر را جهت معرفی تنظیمات به manifest اضافه کنید:
<meta-data
  android:name="ormdroid.database.name"
  android:value="your_database_name" />

<meta-data
  android:name="ormdroid.database.visibility"
  android:value="PRIVATE||WORLD_READABLE||WORLD_WRITEABLE" />
برای آغاز کار این کتابخانه، عبارت زیر را در هرجایی که مایل هستید مانند کلاس ارث بری شده از Application یا در ابتدای هر اکتیویتی که مایل هستید بنویسید. طبق مستندات آن صدا زدن چندباره این متد هیچ اشکالی ندارد.
ORMDroidApplication.initialize(someContext);
معرفی مدل جدول بانک اطلاعاتی هم از طریق ارث بری از کلاس Entity می‌باشد .
public class Person extends Entity {
  public int id;
  public String name;
  public String telephone;
}

//====================

Person p = Entity.query(Person.class).where("id=1").execute();
p.telephone = "555-1234";
p.save();

// یا

Person person = Entity.query(Person.class).where(eql("id", id)).execute();
p.telephone = "555-1234";
p.save();
کد بالا دقیقا یادآوری به EF هست ولی حیف که از Linq پشتیبانی نمی‌شود.
سورس آن در گیت هاب

در اینجا سعی کردیم تعدادی از کتابخانه‌های محبوب را معرفی کنیم ولی تعداد آن به همین جا ختم نمی‌شود. ORM‌های دیگری نظیر AndRom و سایر ORM هایی که در این لیست معرفی شده اند وجود دارند.


نکته نهایی اینکه خوب می‌شود دوستانی که از این ORM‌های مختص اندروید استفاده کرده اند؛ نظراتشان را در مورد آن‌ها بیان کنند و مزایا و معایب آن‌ها را بیان کنند.
مطالب
آشنایی با جنریک‌ها #2
قبل از ادامه آموزش مفاهیم جنریک، در نظر داشتن این نکته ضروری است که مطالبی که در این سری مقالات ارائه می‌شود در سطح مقدماتی است و قصد من آشنا نمودن برنامه نویسانی است که با این مفاهیم ناآشنا هستند ولی با مطالعه این مقاله می‌توانند کدهای تمیزتر و بهتری تولید کنند و همینطور این مفاهیم ساده، پایه‌ای باشد برای فراگیری سایر نکات تکمیلی و پیچیده‌تر جنریک‌ها.

در قسمت قبلی، نحوه تعریف کلاس جنریک شرح داده شد و در سری دوم اشاره‌ای به مفاهیم و نحوه پیاده سازی اینترفیس جنریک می‌پردازیم.
مفهوم اینترفیس جنریک همانند مفهوم اینترفیس در دات نت است. با این تفاوت که برای آن‌ها یک نوع عمومی تعریف می‌شود و نوع آن‌ها در زمان اجرا تعیین خواهد شد و کلاس بر اساس نوع اینترفیس، اینترفیس را پیاده سازی می‌کند. برای درک بهتر به نحوه تعریف اینترفیس جنریک زیر دقت کنید:
public interface IBinaryOperations<T>
{
   T Add(T arg1, T arg2);
   T Subtract(T arg1, T arg2);
   T Multiply(T arg1, T arg2);
   T Divide(T arg1, T arg2);
}
در کد بالا اینترفیسی از نوع جنریک تعریف شده است که دارای چهار متد با چهار خروجی و پارامترهای چنریک می‌باشد که نوع خروجی‌ها و نوع پارامترهای ورودی در زمان استفاده از اینترفیس تعیین می‌شوند که البته در بالا بطور خاص بیان شده است. اینترفیسی داریم که دو ورودی از هر نوعی دریافت می‌کند و چهار عملی اصلی را بر روی آن‌ها انجام داده و خروجی آن‌ها را از همان نوع پارامتر ورودی تولید می‌کند. (بجای اینترفیس‌های مختلف عملیات چهار عمل اصلی برای هر نوع داده (data type)، یک اینترفیس کلی برای تمام data typeها)
در کلاس زیر نحوه پیاده سازی اینترفیس از نوع int را مشاهده می‌کنید که چهار عملی اصلی را برروی داده هایی از نوع int انجام می‌شود و چهار خروجی از نوع int تولید می‌شود.
public class BasicMath : IBinaryOperations<int>
{
   public int Add(int arg1, int arg2)
   { return arg1 + arg2; }
 
   public int Subtract(int arg1, int arg2)
   { return arg1 - arg2; }
 
   public int Multiply(int arg1, int arg2)
   { return arg1 * arg2; }
 
   public int Divide(int arg1, int arg2)
   { return arg1 / arg2; }
}
بعد از پیاده سازی اینترفیس حال نوبت به استفاده از کلاس می‌رسد که زیر نیز نحوه استفاده از کلاس نمایش داده شده است:
static void Main(string[] args)
{
   Console.WriteLine("***** Generic Interfaces *****\n");
   BasicMath m = new BasicMath();
   Console.WriteLine("1 + 1 = {0}", m.Add(1, 1));
   Console.ReadLine();
}
و در صورتیکه بخواهید کلاسی چهار عمل اصلی را بر روی نوع داده double انجام دهد کافیست کلاسی اینترفیس نوع double را پیاده سازی کرده باشد. مانند کد زیر:
public class BasicMath : IBinaryOperations<double>
{
   public double Add(double arg1, double arg2)
   { return arg1 + arg2; }
   ...
}
برداشتی آزاد از این مقاله.
مطالب
C# 7 - Pattern matching and switch expressions
هرچند کار کردن با کلاس‌ها و اینترفیس‌های strongly typed ساده‌تر است، اما گاهی از اوقات نیاز است تا با نوع object کار کرد. به علاوه حتی در حین کار کردن با کلاس‌ها و اینترفیس‌ها هم نیاز است تا نوع خاصی از کلاس‌های مشتق شده را جهت فراخوانی متدی ویژه، بررسی کرد. به همین جهت مفهوم «pattern matching» به C# 7 اضافه شده‌است تا بتوان با سلسله مراتب اشیاء، ساده‌تر کار کرد. برای این منظور اپراتور is و عبارت switch، با الگوهای const ،var و type بهبود و تکامل بخشیده شده‌اند.


استفاده از اپراتور is به همراه pattern matching

اپراتور is از اولین نگارش #C مهیا بوده‌است و هدف آن بررسی تطابق شیءایی خاص، با نوعی مفروض است. برای مثال آیا این نوع مورد بررسی، اینترفیس خاصی را پیاده سازی می‌کند و یا اینکه آیا از کلاسی خاص مشتق شده‌است یا خیر؟ حاصل این بررسی هم true یا false است.
با بهبودهای حاصل شده‌ی در C# 7، اکنون می‌توان از اپراتور is جهت بررسی الگوها نیز استفاده کرد.


الگوی const

در مثال ذیل، آرایه‌ای از اشیاء، شامل یک نال، یک عدد و دو شیء کاربر، تعریف شده‌اند:
public class User
{
    public User(string name)
    {
        Name = name;
    }
 
    public string Name { get; }
}


object[] data = { null, 42, new User("User 1"), new User("User 2") };
foreach (var item in data)
{
    if (item is null) Console.WriteLine("it's a const pattern");
    if (item is 42) Console.WriteLine("it's 42");
}
اولین الگوی مهیای در C# 7، با نام «const pattern» شناخته می‌شود که نمونه‌ای از آن‌را در بدنه‌ی حلقه‌ی فوق مشاهده می‌کنید.
در C# 7 می‌توان اپراتور is را بر روی یک عدد ثابت مانند 42 و یا یک null بکار گرفت. پیش از C# 7 برای بررسی نال بودن یک شیء، تنها از پراتور == می‌شد استفاده کرد.


الگوی Type

دومین الگوی مهیای در C# 7، «الگوی نوع» نام دارد و هدف آن بررسی تطابق یک شیء، با شیءایی دیگر است. مهم‌ترین تفاوت آن با نگارش‌های پیشین سی شارپ این است که اگر اکنون تطابقی تشخیص داده شود، شیء، به متغیر جدید تعریف شده، انتساب داده می‌شود:
object[] data = { null, 42, new User("User 1"), new User("User 2") };
foreach (var item in data)
{
    if (item is int i) Console.WriteLine($"it's a type pattern with an int and the value {i}");
    if (item is User p) Console.WriteLine($"it's a person: {p.Name}");
    if (item is User p2 && p2.Name.StartsWith("U"))
    {
        Console.WriteLine($"it's a person starting with U {p2.Name}");
    }
}
همانطور که ملاحظه می‌کنید اینبار می‌توان پس از اپراتور is، یک متغیر جدید را هم تعریف کرد و در صورت تطابق، این متغیر به صورت خودکار مقدار دهی می‌گردد. به علاوه در اینجا امکان ترکیب شرط‌ها نیز پس از is، مانند سومین if نوشته شده، میسر است.

و یا اکنون قطعه کد قدیمی ذیل را
object obj1 = "Hello, World!";
var str1 = obj1 as string;
if (str1 != null)
{
   Console.WriteLine(str1);
}
می‌توان با pattern matching و استفاده از «الگوی نوع»، به نحو ذیل خلاصه کرد:
object obj2 = "Hello, World!";
if (obj2 is string str2)
{
   Console.WriteLine(str2);
}


الگوی Var

سومین الگوی مهیای در C# 7، الگوی var نام دارد و در این حالت می‌توان بجای ذکر صریح نوع تطابق داده شده، از var استفاده کرد.
بدیهی است این الگو همواره با موفقیت روبرو می‌شود؛ چون var به همان نوع شیء مفروض اشاره می‌کند:
object[] data = { null, 42, new User("User 1"), new User("User 2") };
foreach (var item in data)
{
    if (item is var x) Console.WriteLine($"it's a var pattern with the type {x?.GetType()?.Name}");
}
مهم‌ترین مزیت آن این است که متغیر تعریف شده‌ی پس از var دقیقا دارای همان مقدار و نوع اصلی شیء است و پس از فراخوانی GetType می‌توان به خواص آن دسترسی یافت؛ مانند خاصیت Name ذکر شده‌ی در مثال فوق.
در این حالت اگر item دقیقا null باشد، برای بررسی آن می‌توان از null conditional operator معرفی شده‌ی در C# 6 استفاده کرد.


استفاده از عبارت switch به همراه pattern matching

در C# 7، عبارت switch نیز تکامل یافته‌است. در اینجا الگوهای const ،var و type را نیز می‌توان پس از ذکر case بکار گرفت:
public static void SwitchPattern(object o)
{
    switch (o)
    {
        case null:
            Console.WriteLine("it's a constant pattern");
            break;
        case int i:
            Console.WriteLine("it's an int");
            break;
        case User p when p.Name.StartsWith("U"):
            Console.WriteLine($"a U person {p.Name}");
            break;
        case User p:
            Console.WriteLine($"any other person {p.Name}");
            break;
        case var x:
            Console.WriteLine($"it's a var pattern with the type {x?.GetType().Name} ");
            break;
        default:
            break;
    }
}
الگوهایی را که در اینجا مشاهده می‌کنید دقیقا همان‌هایی هستند که پیشتر بررسی کردیم. الگوی const برای بررسی نال و یک عدد. الگوی type برای بررسی تطابق با یک شیء خاص و سپس استفاده‌ی از آن شیء و الگوی var برای دسترسی به نام نوع مفروض.
تنها نکته‌ی جدید در اینجا، استفاده از واژه‌ی کلیدی when است برای ترکیب شرط‌ها (case User p when p.Name.StartsWith). بنابراین در C# 7 امکان نوشتن case null میسر است؛ به همراه نوشتن شرط‌ها توسط when، در حین تعاریف caseها. به علاوه اینبار عبارت switch محدود به نوع‌های پایه مانند اعداد، رشته‌ها و enums نیست و در اینجا می‌توان یک شیء را نیز مشخص کرد.


شبیه سازی switch موجود در ویژوال بیسیک در C# 7

ویژوال بیسیک از نگارش‌های ابتدایی آن دارای case‌های پیشرفته‌تری است نسبت به #C. برای نمونه در اینجا امکان تعریف تعدادی عدد، استفاده از To و استفاده‌ی از =< را هم مشاهده می‌کنید:
Select Case age
  Case 50
    ageBlock = "the big five-oh"
  Case 80, 81, 82, 83, 84, 85, 86, 87, 88, 89
    ageBlock = "octogenarian"
  Case 90 To 99
    ageBlock = "nonagenarian"
  Case Is >= 100
    ageBlock = "centenarian"
  Case Else
    ageBlock = "just old"
End Select

اکنون در C# 7 می‌توان یک چنین توانمندی را با pattern matching هم پیاده سازی کرد:
string ageBlock;
var age = 40;
switch (age)
{
    case 50:
        ageBlock = "the big five-oh";
        break;
    case var testAge when (new List<int> { 80, 81, 82, 83, 84, 85, 86, 87, 88, 89 }).Contains(testAge):
        ageBlock = "octogenarian";
        break;
    case var testAge when ((testAge >= 90) && (testAge <= 99)):
        ageBlock = "nonagenarian";
        break;
    case var testAge when (testAge >= 100):
        ageBlock = "centenarian";
        break;
    default:
        ageBlock = "just old";
        break;
}
در این مثال یکی از کاربردهای عملی الگوی var را مشاهده می‌کنید؛ یا همان دسترسی به مقدار و نوع وارد شده و سپس اعمال شرط بر روی آن.
همانطور که مشاهده می‌کنید، در قسمت when نیز می‌توان توسط && و || نیز شرط‌ها را ترکیب کرد و یا متدی را با خروجی bool (مانند Contains) بر روی مقدار دریافتی اعمال کرد.
بازخوردهای پروژه‌ها
کندی ایجاد فایل pdf
با سلام . تهیه گزارش زیر نزدیک به 19 ثانیه طول می‌کشد. علت در چیست؟
  public IPdfReportData Create()
        {
            return new PdfReport().DocumentPreferences(doc =>
            {
                //DocumentMargins margin = new DocumentMargins()
                //{
                //    Bottom = 5,
                //    Left = 5,
                //    Right = 5,
                //    Top = 5,
                //};
                //doc.DocumentMargins(margin);
                //PrintingPreferences printPreference = new PrintingPreferences()
                //{
                //    ShowPrintDialogAutomatically = true,
                //};
                //doc.PrintingPreferences(printPreference);
                doc.RunDirection(PdfRunDirection.RightToLeft);
                doc.Orientation(PageOrientation.Landscape);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata
                {
                    Author = "ILIA",
                    Application = "PdfRpt",
                    Keywords = "IList Rpt.",
                    Subject = "ابلاغ استاد",
                    Title = "ابلاغ استاد"
                });
            })
            .DefaultFonts(fonts =>
            {
                fonts.Size(8);
                fonts.Path(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\BYekan.ttf",
                           Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf");
            })
            .PagesFooter(footer =>
            {
                footer.DefaultFooter(DateTime.Now.ToString("MM/dd/yyyy"));
            })
            .PagesHeader(header =>
            {
                //header.PdfFont.Fonts.Add(new iTextSharp.text.Font("BYekan", 10f,0, new BaseColor(Color.Black)));
                //header.PdfFont.Fonts.Add(new iTextSharp.text.Font("BYekan", 10f, new BaseColor(Color.Black)));
                //CH_Rpt_TeacherEblagh ch = new CH_Rpt_TeacherEblagh(TermId.ToString(), "ساری", CenterId.ToString());
                //header.CustomHeader(ch);
                header.DefaultHeader(defaultHeader =>
                {
                    defaultHeader.RunDirection(PdfRunDirection.RightToLeft);
                    //defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png");
                    defaultHeader.Message("ابلاغ استاد");
                });
            })
            .MainTableTemplate(template =>
            {
                template.BasicTemplate(BasicTemplate.SilverTemplate);
            })
            .MainTablePreferences(table =>
            {
                table.ColumnsWidthsType(TableColumnWidthType.Absolute);
                table.NumberOfDataRowsPerPage(0);
            })
            .MainTableDataSource(dataSource =>
            {
                dataSource.StronglyTypedList<ProgramRowSemiInfo>(resultSource);
            })
            .MainTableSummarySettings(summarySettings =>
            {
                //summarySettings.OverallSummarySettings("Summary");
                // summarySettings.PreviousPageSummarySettings("Previous Page Summary");
                // summarySettings.PageSummarySettings("Page Summary");
            })
            .MainTableColumns(columns =>
            {
                columns.AddColumn(column =>
                {
                    column.PropertyName("rowNo");
                    column.IsRowNumber(true);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(0);
                    column.Width(15);
                    column.HeaderCell("#", horizontalAlignment: HorizontalAlignment.Center);
                });
                columns.AddColumn(column =>
                {
                    column.PropertyName<ProgramRowSemiInfo>(x => x.LessonId);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Left);
                    column.IsVisible(true);
                    column.Order(1);
                    column.Width(35);
                    column.HeaderCell("کد درس", horizontalAlignment: HorizontalAlignment.Left);
                });
                columns.AddColumn(column =>
                {
                    column.PropertyName<ProgramRowSemiInfo>(x => x.LessonName);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Left);
                    column.IsVisible(true);
                    column.Order(2);
                    column.Width(80);
                    column.HeaderCell("عنوان درس", horizontalAlignment: HorizontalAlignment.Left);
                });
                columns.AddColumn(column =>
                {
                    column.PropertyName<ProgramRowSemiInfo>(x => x.GroupNumber);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(2);
                    column.Width(20);
                    column.HeaderCell("گروه", horizontalAlignment: HorizontalAlignment.Center);
                });
                //columns.AddColumn(column =>
                //{
                //    column.PropertyName<ProgramRowSemiInfo>(x => x.TrendName);
                //    column.CellsHorizontalAlignment(HorizontalAlignment.Left);
                //    column.IsVisible(true);
                //    column.Order(3);
                //    column.Width(100);
                //    column.HeaderCell("عنوان رشته", horizontalAlignment: HorizontalAlignment.Left);
                //});
                columns.AddColumn(column =>
                {
                    column.PropertyName<ProgramRowSemiInfo>(x => x.TimeShort);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(3);
                    column.Width(30);
                    column.HeaderCell("ساعت", horizontalAlignment: HorizontalAlignment.Center);
                });
                columns.AddColumn(column =>
                {
                    column.PropertyName<ProgramRowSemiInfo>(x => x.DaySemiTitle);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(3);
                    column.Width(15);
                    column.HeaderCell("روز", horizontalAlignment: HorizontalAlignment.Center);
                });
                columns.AddColumn(column =>
                {
                    column.PropertyName<ProgramRowSemiInfo>(x => x.SessionCount);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Left);
                    column.IsVisible(true);
                    column.Order(3);
                    column.Width(15);
                    column.HeaderCell("ت ج", horizontalAlignment: HorizontalAlignment.Left);
                });
                columns.AddColumn(column =>
                {
                    column.PropertyName<ProgramRowSemiInfo>(x => x.SumOfTeachStringShort);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(3);
                    column.Width(25);
                    column.HeaderCell("جمع ساعت", horizontalAlignment: HorizontalAlignment.Center);
                });
                columns.AddColumn(column =>
                {
                    column.PropertyName<ProgramRowSemiInfo>(x => x.PlaceShortName);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Left);
                    column.IsVisible(true);
                    column.Order(3);
                    column.Width(42);
                    column.HeaderCell("ساختمان", horizontalAlignment: HorizontalAlignment.Left);
                });
                columns.AddColumn(column =>
                {
                    column.PropertyName<ProgramRowSemiInfo>(x => x.ClassShortName);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Left);
                    column.IsVisible(true);
                    column.Order(3);
                    column.Width(50);
                    column.HeaderCell("کلاس", horizontalAlignment: HorizontalAlignment.Left);
                });
                int maxSessionCount = resultSource.Max(p => p.SessionCount);
                for (int i = 0; i < maxSessionCount; i++)
                {
                    columns.AddColumn(column =>
                    {
                        column.PropertyName("S" + (i + 1));
                        column.CellsHorizontalAlignment(HorizontalAlignment.Left);
                        column.IsVisible(true);
                        column.Order(4);
                        column.Width(25);
                        column.HeaderCell("ج " + (i + 1), horizontalAlignment: HorizontalAlignment.Center);
                        //column.ColumnItemsTemplate(t => t.CustomTemplate(new SessionCustomCellTemplate()));
                    });
                }
            })
            .MainTableEvents(events =>
            {
                events.DataSourceIsEmpty(message: "داده ای برای مشاهده وجود ندارد.");
            })
            .Export(export =>
            {
                //export.ToExcel();
                //export.ToCsv();
                //export.ToXml();
            })
            .Generate(data => data.AsPdfFile(string.Format("{0}\\{1}", ReportPathHelper.ReportsPath, ReportFileName)));
        }
اینهم سرویسی که این گزارش را ایجاد میکند:
[ServiceContract(Namespace = "")]
    [SilverlightFaultBehavior]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class PdfReportService
    {
        [OperationContract]
        public string CreateReport(int centerId, int termId, int teacherId)
        {
            Stopwatch sw = Stopwatch.StartNew();
            var rpt = new Rpt_TeacherEblagh(centerId, termId, teacherId);
            IPdfReportData rptData = rpt.Create();
            sw.Stop();
            long m = sw.ElapsedMilliseconds;
            string result = rptData.FileName.Replace(HttpRuntime.AppDomainAppPath, string.Empty);
            return result;
        }
    }
این هم خروجی گزارش که تنها یک صفحه دارد ولی باز حدود 20 ثانیه طول می‌کشد:

مطالب دوره‌ها
Lazy loading در تزریق وابستگی‌ها به کمک StructureMap
پیشنیاز این بحث، مطلب «استفاده از StructureMap به عنوان یک IoC Container» می‌باشد که پیشتر در این سری مطالعه کردید (در حد نحوه نصب StructureMap و آشنایی با تنظیمات اولیه آن)

ابتدا ساختار بحث جاری را به نحو زیر درنظر بگیرید:
namespace DI04.Services
{
    public interface IAccounting
    {
        void CreateInvoice(int orderId, int count);
    }
}

namespace DI04.Services
{
    public interface ISales
    {
        bool ShippingAllowed(int orderId);
    }
}

namespace DI04.Services
{
    public interface IOrderHandler
    {
        void Handle(int orderId, int count);
    }
}

using System;

namespace DI04.Services
{
    public class Accounting : IAccounting
    {
        public Accounting()
        {
            Console.WriteLine("Accounting ctor.");
        }

        public void CreateInvoice(int orderId, int count)
        {
            // ...
        }
    }
}

using System;

namespace DI04.Services
{
    public class Sales : ISales
    {
        public Sales()
        {
            Console.WriteLine("Sales ctor.");
        }

        public bool ShippingAllowed(int orderId)
        {
            // فقط جهت آزمایش سیستم
            return false;
        }
    }
}

using System;

namespace DI04.Services
{
    public class OrderHandler : IOrderHandler
    {
        private readonly IAccounting _accounting;
        private readonly ISales _sales;
        public OrderHandler(IAccounting accounting, ISales sales)
        {
            Console.WriteLine("OrderHandler ctor.");
            _accounting = accounting;
            _sales = sales;
        }

        public void Handle(int orderId, int count)
        {
            if (_sales.ShippingAllowed(orderId))
            {
                _accounting.CreateInvoice(orderId, count);
            }
        }
    }
}
در اینجا کار مدیریت سفارشات در کلاس OrderHandler انجام می‌شود. این کلاس دارای دو وابستگی تزریق شده در سازنده کلاس می‌باشد.
در متد Handle، اگر مجوز کار توسط متد ShippingAllowed صادر شد، آنگاه کار نهایی توسط متد CreateInvoice باید صورت گیرد. با توجه به اینکه تزریق وابستگی‌ها در سازنده کلاس صورت می‌گیرد، نیاز است پیش از وهله سازی کلاس OrderHandler، هر دو وابستگی آن وهله سازی و تزریق شوند. در حالیکه در مثال جاری هیچگاه به وهله‌ای از نوع IAccounting نیاز نخواهد شد؛ زیرا متد ShippingAllowed در این مثال، فقط false بر می‌گرداند.
و از این نمونه‌ها زیاد هستند. کلاس‌هایی با تعداد متدهای بالا و تعداد وابستگی‌های قابل توجه که ممکن است در طول عمر شیء وهله سازی شده این کلاس، تنها به یکی از این وابستگی‌ها نیاز شود و نه به تمام آن‌ها.
راه حلی برای این مساله در دات نت 4 با معرفی کلاس Lazy ارائه شده است؛ به این نحو که اگر برای مثال در اینجا accounting را Lazy تعریف کنیم، تنها زمانی وهله سازی خواهد شد که به آن نیاز باشد و نه پیش از آن.
 private readonly Lazy<IAccounting> _accounting;

سؤال: Lazy loading تزریق وابستگی‌ها را چگونه می‌توان توسط StructureMap فعال ساخت؟

ابتدا تعاریف کلاس  OrderHandlerرا به نحو زیر بازنویسی می‌کنیم:
using System;

namespace DI04.Services
{
    public class OrderHandlerLazy : IOrderHandler
    {
        private readonly Lazy<IAccounting> _accounting;
        private readonly Lazy<ISales> _sales;
        public OrderHandlerLazy(Lazy<IAccounting> accounting, Lazy<ISales> sales)
        {
            Console.WriteLine("OrderHandlerLazy ctor.");
            _accounting = accounting;
            _sales = sales;
        }

        public void Handle(int orderId, int count)
        {
            if (_sales.Value.ShippingAllowed(orderId))
            {
                _accounting.Value.CreateInvoice(orderId, count);
            }
        }
    }
}
در اینجا سازنده‌های کلاس، به صورت Lazy معرفی شده‌اند. دسترسی به فیلدهای sales و accounting نیز اندکی تغییر کرده‌اند و اینبار از طریق خاصیت Value آن‌ها باید انجام شود.
مرحله نهایی هم اندکی تغییر در نحوه معرفی تنظیمات اولیه StructureMap است:
using System;
using DI04.Services;
using StructureMap;

namespace DI04
{
    class Program
    {
        static void Main(string[] args)
        {
            // تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود
            ObjectFactory.Initialize(x =>
            {
                x.For<IOrderHandler>().Use<OrderHandlerLazy>();

                // Lazy loading
                x.For<Lazy<IAccounting>>().Use(c => new Lazy<IAccounting>(c.GetInstance<Accounting>));
                x.For<Lazy<ISales>>().Use(c => new Lazy<ISales>(c.GetInstance<Sales>));
            });

            var orderHandler = ObjectFactory.GetInstance<IOrderHandler>();
            orderHandler.Handle(orderId: 1, count: 10);
        }
    }
}
به این ترتیب زمانیکه برنامه به sales.Value می‌رسد آنگاه نیاز به وهله سازی شیء متناظر با آن‌را خواهد داشت که در اینجا از طریق متد GetInstance به آن ارسال خواهد گردید.

خروجی برنامه در این حالت:
OrderHandlerLazy ctor.
Sales ctor.
همانطور که مشاهده می‌کنید، هرچند کلاس OrderHandlerLazy دارای دو وابستگی تعریف شده در سازنده کلاس است، اما تنها وابستگی Sales آن زمانیکه به آن نیاز شده، وهله سازی گردیده است و خبری از وهله سازی کلاس Accounting نیست (چون مطابق تعاریف کلاس‌های برنامه هیچگاه به مسیر accounting.Value نخواهیم رسید؛ بنابراین نیازی هم به وهله سازی آن نخواهد بود).


دریافت مثال این قسمت
DI04.zip
مطالب
Blazor 5x - قسمت 15 - کار با فرم‌ها - بخش 3 - ویرایش اطلاعات
در قسمت قبل، ویژگی‌های ثبت اطلاعات یک اتاق جدید و سپس نمایش لیست آن‌ها را تکمیل کردیم. در این قسمت می‌خواهیم امکان ویرایش آن‌ها را نیز اضافه کنیم.


افزودن دکمه‌ی ویرایش، به رکوردهای لیست اتاق‌ها و نمایش جزئیات رکورد در حال ویرایش

تا اینجا کامپوننت Pages\HotelRoom\HotelRoomUpsert.razor دارای یک چنین مسیریابی است:
@page "/hotel-room/create"
در ادامه می‌خواهیم اگر کاربری برای مثال به آدرس hotel-room/edit/1 مراجعه کرد، اطلاعات رکورد متناظر با آن نمایش داده شود؛ تا بتواند آن‌ها را ویرایش کند. یعنی می‌خواهیم از همین صفحه، هم برای ویرایش اطلاعات موجود و هم برای ثبت اطلاعات جدید استفاده کنیم. بنابراین نیاز به تعریف مسیریابی دومی در کامپوننت HotelRoomUpsert وجود دارد:
@page "/hotel-room/create"
@page "/hotel-room/edit/{Id:int}"
در اینجا مسیریابی دوم تعریف شده، یک پارامتر مقید شده‌ی به نوع int را انتظار دارد. مزیت این مقید سازی، نمایش خودکار صفحه‌ی «یافت نشد» تعریف شده‌ی در فایل BlazorServer.App\App.razor، با ورود اطلاعاتی غیر عددی است. مسیریابی اول، برای ثبت اطلاعات مورد استفاده قرار می‌گیرد و مسیریابی دوم، برای ویرایش اطلاعات.
پس از تعریف مسیریابی دریافت پارامتر Id رکورد در حال ویرایش، نحوه‌ی واکنش نشان دادن به آن در کامپوننت HotelRoomUpsert.razor به صورت زیر است:
@code
{
    // ...

    [Parameter] public int? Id { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (Id.HasValue)
        {
            // Update Mode
            Title = "Update";
            HotelRoomModel = await HotelRoomService.GetHotelRoomAsync(Id.Value);
        }
        else
        {
            // Create Mode
            HotelRoomModel = new HotelRoomDTO();
        }
    }

    // ...
}
- در اینجا پارامتر عددی Id مسیریابی را از نوع nullable تعریف کرده‌ایم. علت اینجا است که اگر کاربر با مسیریابی اول تعریف شده، به این کامپوننت برسد، یعنی قصد ثبت اطلاعات جدیدی را دارد. بنابراین ذکر Id، اختیاری است.
- سپس می‌خواهیم در زمان بارگذاری کامپوننت جاری، اگر مقدار Id، تنظیم شده بود، تمام فیلدهای فرم متصل به شیء HotelRoomModel را به صورت خودکار بر اساس اطلاعات رکورد متناظر با آن در بانک اطلاعاتی، مقدار دهی اولیه کنیم.
<EditForm Model="HotelRoomModel" OnValidSubmit="HandleHotelRoomUpsert">
چون فرم جاری توسط کامپوننت EditForm تعریف شده‌است و مدل آن به HotelRoomModel متصل است، بر اساس two-way binding‌های تعریف شده‌ی در اینجا، اگر مقدار Model را به روز رسانی کنیم، به صورت خودکار تمام فیلدهای متصل به آن نیز مقدار دهی اولیه خواهند شد.
کار مقدار دهی اولیه‌ی فیلدهای یک کامپوننت نیز باید در روال رویداد گردان OnInitializedAsync انجام شود که نمونه‌ای از آن را در کدهای فوق مشاهده می‌کنید. در این مثال اگر Id مقدار داشته باشد، مقدار آن‌را به متد GetHotelRoomAsync ارسال کرده و سپس شیء DTO دریافتی از آن‌را به مدل فرم انتساب می‌دهیم تا فرم ویرایشی، قابل استفاده شود:


در ادامه برای ساده سازی رسیدن به مسیرهایی مانند hotel-room/edit/1، به کامپوننت Pages\HotelRoom\HotelRoomList.razor مراجعه کرده و در همان ردیفی که اطلاعات رکورد یک اتاق را نمایش می‌دهیم، لینکی را نیز به صفحه‌ی ویرایش آن، اضافه می‌کنیم:
<tr>
    <td>@room.Name</td>
    <td>@room.Occupancy</td>
    <td>@room.RegularRate.ToString("c")</td>
    <td>@room.SqFt</td>
    <td>
        <NavLink href="@($"hotel-room/edit/{room.Id}")" class="btn btn-primary">Edit</NavLink>
    </td>
</tr>
برای این منظور فقط کافی است در جائیکه tr هر رکورد رندر می‌شود، یک td مخصوص NavLink منتهی به hotel-room/edit/{room.Id} را نیز تعریف کنیم:



مدیریت ثبت اطلاعات ویرایش شده‌ی یک اتاق، در بانک اطلاعاتی

در حین تکمیل این قسمت می‌خواهیم پیام‌هایی مانند موفقیت آمیز بودن عملیات را نیز به کاربر نمایش دهیم. به همین جهت مراحل «Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت» را برای افزودن کتابخانه‌ی معروف جاوا اسکریپتی Toastr طی می‌کنیم که شامل این قسمت‌ها است:
- دریافت و نصب بسته‌های jquery و toastr
- اصلاح فایل Pages\_Host.cshtml برای افزودن مداخل فایل‌های CSS و JS بسته‌های نصب شده
- تعریف فایل جدید wwwroot\js\common.js برای سادگی کار با توابع جاوا اسکریپتی toastr و افزودن مدخل آن به فایل Pages\_Host.cshtml
- تعریف متدهای الحاقی JSRuntimeExtensions ، جهت کاهش کدهای تکراری فراخوانی متدهای toastr و افزودن فضای نام آن به فایل Imports.razor_

جزئیات این موارد را در قسمت یازدهم این سری می‌توانید مطالعه کنید و از تکرار آن‌ها در اینجا صرفنظر می‌شود. همچنین کدهای تکمیل شده‌ی این قسمت را از انتهای مطلب جاری نیز می‌توانید دریافت کنید.

همچنین پیش از تکمیل ادامه‌ی کدهای ویرایش اطلاعات، نیاز است متد IsRoomUniqueAsync را به صورت زیر اصلاح کنیم:
namespace BlazorServer.Services
{
    public interface IHotelRoomService
    {
        Task<bool> IsRoomUniqueAsync(string name, int roomId);

        // ...
    }
}

namespace BlazorServer.Services
{
    public class HotelRoomService : IHotelRoomService
    {
        // ...
 
        public Task<bool> IsRoomUniqueAsync(string name, int roomId)
        {
            if (roomId == 0)
            {
                // Create Mode
                return _dbContext.HotelRooms
                                .ProjectTo<HotelRoomDTO>(_mapperConfiguration)
                                .AnyAsync(x => x.Name != name);
            }
            else
            {
                // Edit Mode
                return _dbContext.HotelRooms
                                .ProjectTo<HotelRoomDTO>(_mapperConfiguration)
                                .AnyAsync(x => x.Name != name && x.Id != roomId);
            }
        }
    }
}
پیشتر در قست 13، بررسی منحصربفرد بودن نام اتاق، از طریق بررسی وجود نام آن در بانک اطلاعاتی صورت می‌گرفت. اینکار در حین ثبت اطلاعات یک رکورد جدید (Id==0) مشکلی را ایجاد نمی‌کند. اما اگر در حال ویرایش اطلاعات باشیم، چون این نام پیشتر ثبت شده‌است، پیام تکراری بودن نام اتاق را دریافت می‌کنیم؛ حتی اگر در اینجا نام اتاق در حال ویرایش را تغییر نداده باشیم و همان نام قبلی باشد. به همین جهت نیاز است در حالت ویرایش اطلاعات، اگر نامی در سایر رکوردها و نه رکوردی با Id مساوی فرم در حال ویرایش، با نام جدید یکی بود، آنگاه آن نام را به صورت تکراری گزارش دهیم که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید.

اکنون متد HandleHotelRoomUpsert کامپوننت Pages\HotelRoom\HotelRoomUpsert.razor جهت مدیریت ثبت و ویرایش اطلاعات به صورت زیر تغییر می‌کند:
// ...
@inject IJSRuntime JsRuntime


@code
{
   // ...

    private async Task HandleHotelRoomUpsert()
    {
        var isRoomUnique = await HotelRoomService.IsRoomUniqueAsync(HotelRoomModel.Name, HotelRoomModel.Id);
        if (!isRoomUnique)
        {
            await JsRuntime.ToastrError($"The room name: `{HotelRoomModel.Name}` already exists.");
            return;
        }

        if (HotelRoomModel.Id != 0 && Title == "Update")
        {
            // Update Mode
            var updateResult = await HotelRoomService.UpdateHotelRoomAsync(HotelRoomModel.Id, HotelRoomModel);
            await JsRuntime.ToastrSuccess($"The `{HotelRoomModel.Name}` updated successfully.");
        }
        else
        {
            // Create Mode
            var createResult = await HotelRoomService.CreateHotelRoomAsync(HotelRoomModel);
            await JsRuntime.ToastrSuccess($"The `{HotelRoomModel.Name}` created successfully.");
        }

        NavigationManager.NavigateTo("hotel-room");
    }
}
- در ابتدا چون می‌خواهیم پیام‌هایی را توسط Toastr نمایش دهیم، سرویس IJSRuntime را به کامپوننت جاری تزریق کرده‌ایم که این سرویس، امکان دسترسی به متدهای الحاقی ToastrError و ToastrSuccess تعریف شده‌ی در فایل Utils\JSRuntimeExtensions.cs را می‌دهد (کدهای آن در قسمت 11 ارائه شدند).
- در ابتدا عدم تکراری بودن نام اتاق بررسی می‌شود:


- در آخر بر اساس Id مدل فرم، حالت ویرایش و یا ثبت اطلاعات را تشخیص می‌دهیم. البته Id مدل فرم، در حالت ثبت اطلاعات نیز صفر است؛ به علت فراخوانی ()HotelRoomModel = new HotelRoomDTO که سبب مقدار دهی Id آن با عدد پیش‌فرض صفر می‌شود. بنابراین صرفا بررسی Id مدل، کافی نیست و برای مثال می‌توان عنوان تنظیم شده‌ی در متد OnInitializedAsync را نیز بررسی کرد.
- پس از تشخیص حالت ویرایش و یا ثبت، یکی از متدهای متناظر HotelRoom Service را مانند UpdateHotelRoomAsync و یا CreateHotelRoomAsync فراخوانی کرده و سپس پیامی را به کاربر نمایش داده و او را به صفحه‌ی نمایش لیست اتاق‌ها، هدایت می‌کنیم:




کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-15.zip
نظرات مطالب
C# 7 - Ref Returns and Ref Locals
یک نکته‌ی تکمیلی: امکان تعریف خروجی از نوع ref readonly در C# 7.2

modifier جدیدی در C# 7.2 به نام ref readonly جهت تعریف نوع خروجی متدها نیز معرفی شده‌است. به این ترتیب یک متد می‌تواند بازگشت ارجاعی به اطلاعاتی موجود را بیان و همچنین فراخوان را از تغییر آن منع کند.
البته فراخوان می‌تواند تصمیم گیری کند که آیا یک کپی و یا یک ارجاع فقط خواندنی را از این متد ویژه دریافت کند. به این معنا که خروجی از نوع ref readonly، فراخوان را ملزم به تعریف یک متغیر محلی از نوع ref readonly نمی‌کند.

در مثال زیر، متد ReturnBiggestA یک خروجی کپی را باز می‌گرداند و متد ReturnBiggestARefReadonly دقیقا ارجاعی را به DataInfo اصلی بازگشت می‌دهد و با آن یکی است:
namespace CS72Tests
{
    public struct DataInfo
    {
        public double A;
    }

    public class RefReadonlyExamples
    {
        public DataInfo ReturnBiggestA(in DataInfo data1, in DataInfo data2)
        {
            return data1.A > data2.A ? data1 : data2;
        }

        public ref readonly DataInfo ReturnBiggestARefReadonly(in DataInfo data1, in DataInfo data2)
        {
            if (data1.A > data2.A)
            {
                return ref data1;
            }
            return ref data2;
        }

        public void TestingRefReadonly()
        {
            var data1 = new DataInfo { A = 0 };
            var data2 = new DataInfo { A = 100 };

            var biggest = ReturnBiggestA(data1, data2);
            biggest.A = 42;


            var biggest2 = ReturnBiggestARefReadonly(data1, data2);
            biggest2.A = 99;


            ref readonly var biggest3 = ref ReturnBiggestARefReadonly(data1, data2);
            biggest3.A = 99; // ERROR: The left-hand side of an assignment must be a variable, property or indexer
        }
    }
}
- در این فراخوانی‌ها، biggest یک کپی از data2 را باز می‌گرداند. به همین جهت می‌توان A آن‌را تغییر داد.
- در اولین فراخوانی ReturnBiggestARefReadonly، با تعریف خروجی به صورت var biggest2، یک کپی از data2 را دریافت کرده‌ایم. به همین جهت A آن قابل تغییر است.
- اما در دومین فراخوانی ReturnBiggestARefReadonly، چون خروجی آن‌را از نوع ref readonly var دریافت کرده‌ایم، این خروجی به data2 اصلی اشاره می‌کند و همچنین فقط خواندنی است. بنابراین سطر بعدی آن که A را تغییر می‌دهد، مجاز نیست.

پ.ن
در ابتدا قصد داشتند ref readonly را برای تعریف پارامترهای value type نیز بکار برند، اما این تصمیم با معرفی پارامترهای از نوع in جایگزین شد. به همین جهت ممکن است مقالات قدیمی‌تر C# 7.2 را با تعریف متدهایی مانند ذیل نیز مشاهده کنید که در نگارش آخر C# 7.2، تمام این‌ها به in تغییر کرده‌اند:
public static void Add(ref readonly int x, ref readonly int y, ref int z)
{
   z = x + y + z;
}
مطالب
ویژگی های کمتر استفاده شده در NET. - بخش دوم

Curry and Partial methods

Curry – در ریاضیات و علوم کامپیوتر، currying روشی است برای ترجمه تابعی که آرگومان‌های متعددی می‌گیرد و به صورت ارزیابی دنباله‌ای‌است از توابع که هر کدام یک آرگومان دارند.
برای پیاده سازی آن در #C، از extension methods استفاده می‌کنیم.
public static class CurryMethodExtensions
{
    public static Func< A, Func< B, Func< C, R > > > Curry< A, B, C, R >( this Func< A, B, C, R > f )
    {
        return a => b => c => f( a, b, c );
    }
}
مثالی برای استفاده از متد بالا:
Func< int, int, int, int > addNumbers = ( x, y, z ) => x + y + z;
var f1 = addNumbers.Curry();
Func< int, Func< int, int > > f2 = f1( 3 );
Func< int, int > f3 = f2( 4 );
Console.WriteLine( f3( 5 ) );
بعد از فراخوانی متد Curry می‌توان از کلمه کلیدی var در دستورات بعدی بجای تعریف نوع متغیرها استفاده کرد.
نحوه اجرای دستورات بالا را در تصویر زیر می‌توانید مشاهده کنید:


Partial – در علوم کامپیوتر، قسمتی از یک برنامه (یا قسمتی از یک تابع برنامه) است که اشاره به روند تثبیت تعدادی از آرگومان‌ها به یک تابع و تولید تعداد آرگومان‌های کمتر تابع دیگری را می‌گویند.
public static class CurryMethodExtensions
{
    public static Func< C, R > Partial< A, B, C, R >( this Func< A, B, C, R > f, A a, B b )
    {
        return c => f( a, b, c );
    }
}
مثالی برای استفاده از تابع بالا:
Func< int, int, int, int > sumNumbers = ( x, y, z ) => x + y + z;
Func< int, int > f4 = sumNumbers.Partial( 3, 4 );
Console.WriteLine( f4( 5 ) );
بعد از فراخوانی متد Curry می‌توان از کلمه کلیدی var در دستورات بعدی بجای تعریف نوع متغیرها استفاده کرد.
نحوه اجرای دستورات بالا را در تصویر زیر می‌توانید مشاهده کنید:

WeakReference

یک ارجاع ضعیف به GC اجازه می‌دهد که یک شیء را جمع آوری کند، در عین حالی که برنامه امکان دسترسی به آن را خواهد داشت. در صورتیکه نیاز به شیء‌ای داشته باشید، می‌توانید یک ارجاع قوی را از آن داشته باشید و از جمع آوری آن توسط GC جلوگیری کنید.
var obj = new WeakReferenceTest
            {
                FirstName = "Vahid"
            };
var w = new WeakReference(obj);
obj = null;
GC.Collect();
var weakReferenceTest = w.Target as WeakReferenceTest;
if ( weakReferenceTest != null )
    Console.WriteLine( weakReferenceTest.FirstName );

Lazy<T>

برای ایجاد یک شیء بزرگ، پردازش زیاد منابع و یا اجرای یک وظیفه (task) با پردازش زیاد منابع، به خصوص در زمانیکه ایجاد و یا اجرای این فرآیند در طول عمر یک برنامه، ممکن است هرگز رخ ندهد، از Lazy استفاده می‌شود.
public abstract class ThreadSafeLazyBaseSingleton< T > where T : new()
{
    static readonly Lazy< T > lazy = new Lazy< T >( () => new T() );

    public static T Instance => lazy.Value;
}

BigInteger

نوع داده BigInteger یک نوع تغییر ناپذیر (immutable type) و نمایانگر یک عدد صحیح بزرگ دلخواه است که مقدار آن در تئوری در هیچ حد و مرز حداقل و حداکثری نیست. این نوع، از دیگر انواع جدایی ناپذیر (integral types) در NET.، که دارای خصوصیت MinValue و  MaxValue هستند، متفاوت است.
var positiveString = "91389681247993671255433422114345532000000";
var negativeString = "-9031583741089631207100208803453423537140000";
var posBigInt = BigInteger.Parse( positiveString );
Console.WriteLine( posBigInt );
var negBigInt = BigInteger.Parse( negativeString );
Console.WriteLine( negBigInt );
نکته: از آنجایی که BigInteger یک نوع تغییر ناپذیر و بدون حد و مرز حداقل و حداکثر است، برای بعضی از عملیات‌، اگر مقدار آن را بسیار زیاد افزایش دهید خطای OutOfMemoryException رخ می‌دهد (البته من با 1024 بار ضرب متغیر positiveString در خودش هم نتوانستم این پیام خطا را ببینم).
مطالب دوره‌ها
ایجاد کاتالوگ‌های Full text search و ایندکس‌های آن
جستجو بر روی خواص و متادیتای اسناد آفیس

همانطور که در قسمت قبل نیز عنوان شد، فیلترهای FTS آفیس، علاوه بر اینکه امکان جستجوی پیشرفته FTS را بر روی کلیه فایل‌های مجموعه آفیس میسر می‌کنند، امکان جستجوی FTS را بر روی خواص ویژه اضافی آن‌ها، مانند نام نویسنده، واژه‌های کلیدی، تاریخ ایجاد و امثال آن نیز به همراه دارند.
اینکه چه خاصیتی را بتوان جستجو کرد نیز بستگی به نوع فیلتر نصب شده دارد. برای تعریف خواص قابل جستجوی یک سند، باید یک SEARCH PROPERTY LIST را ایجاد کرد:
CREATE SEARCH PROPERTY LIST WordSearchPropertyList;
GO

ALTER SEARCH PROPERTY LIST WordSearchPropertyList
   ADD 'Authors'
   WITH (PROPERTY_SET_GUID = 'F29F85E0-4FF9-1068-AB91-08002B27B3D9',
             PROPERTY_INT_ID = 4,
             PROPERTY_DESCRIPTION = 'System.Authors - authors of a given item.');
GO
در این تعریف، PROPERTY_INT_ID و PROPERTY_SET_GUIDها استاندارد بوده و لیست آن‌ها را در آدرس ذیل می‌توانید مشاهده نمائید:


بهبود کیفیت جستجو توسط Stop lists و Stop words

به یک سری از کلمات و حروف، اصطلاحا noise words گفته می‌شود. برای مثال در زبان انگلیسی حروف و کلماتی مانند a، is، the و and به صورت خودکار از FTS حذف می‌شوند؛ چون جستجوی آن‌ها بی‌حاصل است. به این‌ها stop words نیز می‌گویند.
با استفاده از کوئری ذیل می‌توان لیست stop words تعریف شده در بانک اطلاعاتی جاری را مشاهده کرد:
 -- Check the Stopwords list
SELECT w.stoplist_id,
   l.name,
   w.stopword,
   w.language
FROM sys.fulltext_stopwords AS w
   INNER JOIN sys.fulltext_stoplists AS l
     ON w.stoplist_id = l.stoplist_id;
و برای تعریف stop words از دستورات ذیل کمک گرفته می‌شود:
 -- Stopwords list
CREATE FULLTEXT STOPLIST SQLStopList;
GO

-- Add a stopword
ALTER FULLTEXT STOPLIST SQLStopList
 ADD 'SQL' LANGUAGE 'English';
GO


کاتالوگ‌های Full Text Search

ایندکس‌های ویژه‌ی FTS، در مکان‌هایی به نام Full Text Catalogs ذخیره می‌شوند. این کاتالوگ‌ها صرفا یک شیء مجازی بوده و تنها برای تعریف ظرفی دربرگیرنده‌ی ایندکس‌های FTS تعریف می‌شوند. در نگارش‌های پیش از 2012 اس کیوال سرور، این کاتالوگ‌ها اشیایی فیزیکی بودند؛ اما اکنون تبدیل به اشیایی مجازی شده‌اند.
حالت کلی تعریف یک fulltext catalog به نحو ذیل است:
 create fulltext catalog catalog_name
on filegroup filegroup_name
in path  'rootpath'
with some_options
as default
authoriztion owner_name
accent_sensivity = {on|off}
اما اکثر گزینه‌های آن مانند on filegroup و in path صرفا برای حفظ سازگاری با نگارش‌های قبلی حضور دارند و دیگر نیازی به ذکر آن‌ها نیست؛ چون تعریف کننده‌ی ماهیت فیزیکی این کاتالوگ‌ها می‌باشند.
به صورت پیش فرض حساسیت به لهجه یا accent_sensivity خاموش است. اگر روشن شود، باید کل ایندکس مجددا بازسازی شود.


ایجاد ایندکس‌های Full Text

پس از ایجاد یک fulltext catalog، اکنون نوبت به تعریف ایندکس‌هایی فیزیکی هستند که داخل این کاتالوگ‌ها ذخیره خواهند شد:
 -- Full-text catalog
CREATE FULLTEXT CATALOG DocumentsFtCatalog;
GO

-- Full-text index
CREATE FULLTEXT INDEX ON dbo.Documents
(
  docexcerpt Language 1033,
  doccontent TYPE COLUMN doctype
  Language 1033
  STATISTICAL_SEMANTICS
)
KEY INDEX PK_Documents
ON DocumentsFtCatalog
WITH STOPLIST = SQLStopList,
  SEARCH PROPERTY LIST = WordSearchPropertyList,
  CHANGE_TRACKING AUTO;
GO
در اینجا توسط KEY INDEX نام منحصربفرد ایندکس مشخص می‌شود.
CHANGE_TRACKING AUTO به این معنا است که SQL Server به صورت خودکار کار به روز رسانی این ایندکس را با تغییرات رکوردها انجام خواهد داد.
ذکر STATISTICAL_SEMANTICS، منحصر به SQL Server 2012 بوده و کار آن تشخیص واژه‌های کلیدی و ایجاد ایندکس‌های یافتن اسناد مشابه است. برای استفاده از آن حتما نیاز است مطابق توضیحات قسمت قبل، Semantic Language Database پیشتر نصب شده باشد.
توسط STOPLIST، لیست واژه‌هایی که قرار نیست ایندکس شوند را معرفی خواهیم کرد. SQLStopList را در ابتدای بحث ایجاد کردیم.
Language 1033 به معنای استفاده از زبان US English است.
نحوه‌ی استفاده از SEARCH PROPERTY LIST ایی که پیشتر تعریف کردیم را نیز در اینجا ملاحظه می‌کنید.


مثالی برای ایجاد ایندکس‌های FTS

برای اینکه ربط منطقی نکات عنوان شده را بهتر بتوانید بررسی و آزمایش کنید، مثال ذیل را درنظر بگیرید.
ابتدا جدول Documents را برای ذخیره سازی تعدادی سند، ایجاد می‌کنیم:
 CREATE TABLE dbo.Documents
(
  id INT IDENTITY(1,1) NOT NULL,
  title NVARCHAR(100) NOT NULL,
  doctype NCHAR(4) NOT NULL,
  docexcerpt NVARCHAR(1000) NOT NULL,
  doccontent VARBINARY(MAX) NOT NULL,
  CONSTRAINT PK_Documents
PRIMARY KEY CLUSTERED(id)
);
اگر به این جدول دقت کنید، هدف از آن ذخیره‌ی اسناد آفیس است که فیلترهای FTS آن‌را در قسمت قبل نصب کردیم. ستون doctype، معرف نوع سند و doccontent ذخیره کننده‌ی محتوای کامل سند خواهند بود.

سپس اطلاعاتی را در این جدول ثبت می‌کنیم:
-- Insert data
-- First row
INSERT INTO dbo.Documents
(title, doctype, docexcerpt, doccontent)
SELECT N'Columnstore Indices and Batch Processing', 
 N'docx',
 N'You should use a columnstore index on your fact tables,
   putting all columns of a fact table in a columnstore index. 
   In addition to fact tables, very large dimensions could benefit 
   from columnstore indices as well. 
   Do not use columnstore indices for small dimensions. ',
 bulkcolumn
FROM OPENROWSET
 (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\ColumnstoreIndicesAndBatchProcessing.docx', 
  SINGLE_BLOB) AS doc;

-- Second row
INSERT INTO dbo.Documents
(title, doctype, docexcerpt, doccontent)
SELECT N'Introduction to Data Mining', 
 N'docx',
 N'Using Data Mining is becoming more a necessity for every company 
   and not an advantage of some rare companies anymore. ',
 bulkcolumn
FROM OPENROWSET
 (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\IntroductionToDataMining.docx', 
  SINGLE_BLOB) AS doc;

-- Third row
INSERT INTO dbo.Documents
(title, doctype, docexcerpt, doccontent)
SELECT N'Why Is Bleeding Edge a Different Conference', 
 N'docx',
 N'During high level presentations attendees encounter many questions. 
   For the third year, we are continuing with the breakfast Q&A session. 
   It is very popular, and for two years now, 
   we could not accommodate enough time for all questions and discussions! ',
 bulkcolumn
FROM OPENROWSET
 (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\WhyIsBleedingEdgeADifferentConference.docx', 
  SINGLE_BLOB) AS doc;

-- Fourth row
INSERT INTO dbo.Documents
(title, doctype, docexcerpt, doccontent)
SELECT N'Additivity of Measures', 
 N'docx',
 N'Additivity of measures is not exactly a data warehouse design problem. 
   However, you have to realize which aggregate functions you will use 
   in reports for which measure, and which aggregate functions 
   you will use when aggregating over which dimension.',
 bulkcolumn
FROM OPENROWSET
 (BULK 'C:\Users\Vahid\Desktop\Updates\fts_docs\AdditivityOfMeasures.docx', 
  SINGLE_BLOB) AS doc;
GO
4 ردیف ثبت شده در جدول اسناد، نیاز به 4 فایل docx نیز دارند که آن‌ها را از آدرس ذیل می‌توانید برای تکمیل ساده‌تر آزمایش دریافت کنید:
fts_docs.zip

در ادامه می‌خواهیم قادر باشیم تا بر روی متادیتای نویسنده‌ی این اسناد نیز جستجوی کامل FTS را انجام دهیم. به همین جهت SEARCH PROPERTY LIST آن‌را نیز ایجاد خواهیم کرد:
 -- Search property list
CREATE SEARCH PROPERTY LIST WordSearchPropertyList;
GO
ALTER SEARCH PROPERTY LIST WordSearchPropertyList
 ADD 'Authors'
 WITH (PROPERTY_SET_GUID = 'F29F85E0-4FF9-1068-AB91-08002B27B3D9',
PROPERTY_INT_ID = 4,
PROPERTY_DESCRIPTION = 'System.Authors - authors of a given item.');
GO
همچنین می‌خواهیم از واژه‌ی SQL در این اسناد، در حین ساخت ایندکس‌های FTS صرفنظر شود. برای این منظور یک FULLTEXT STOPLIST را به نام SQLStopList ایجاد کرده و سپس واژه‌ی مدنظر را به آن اضافه می‌کنیم:
 -- Stopwords list
CREATE FULLTEXT STOPLIST SQLStopList;
GO
-- Add a stopword
ALTER FULLTEXT STOPLIST SQLStopList
 ADD 'SQL' LANGUAGE 'English';
GO
صحت عملیات آن‌را توسط کوئری «Check the Stopwords list» ذکر شده در ابتدای بحث می‌توانید بررسی کنید.

اکنون زمان ایجاد یک کاتالوگ FTS است:
 -- Full-text catalog
CREATE FULLTEXT CATALOG DocumentsFtCatalog;
GO
با توجه به اینکه در نگارش‌های جدید SQL Server این کاتالوگ صرفا ماهیتی مجازی دارد، ساده‌ترین Syntax آن برای کار ما کفایت می‌کند.
و در آخر ایندکس FTS ایی را که پیشتر در مورد آن بحث کردیم، ایجاد خواهیم کرد:
 -- Full-text index
CREATE FULLTEXT INDEX ON dbo.Documents
(
  docexcerpt Language 1033,
  doccontent TYPE COLUMN doctype
  Language 1033
  STATISTICAL_SEMANTICS
)
KEY INDEX PK_Documents
ON DocumentsFtCatalog
WITH STOPLIST = SQLStopList,
  SEARCH PROPERTY LIST = WordSearchPropertyList,
  CHANGE_TRACKING AUTO;
GO



در این تصویر محل یافتن اجزای مختلف Full text search را در management studio مشاهده می‌کنید.


یک نکته‌ی تکمیلی
برای زبان فارسی نیز یک سری stop words وجود دارند. لیست آن‌ها را از اینجا می‌توانید دریافت کنید:
stopwords.sql
متاسفانه زبان فارسی جزو زبان‌های پشتیبانی شده توسط FTS در SQL Server نیست (نه به این معنا که نمی‌توان با آن کار کرد؛ به این معنا که برای مثال دستورات صرفی زبان را ندارد) و به همین جهت از زبان انگلیسی در اینجا استفاده شده‌است.