اشتراک‌ها
شرکت در نظرسنجی امکانات بومی سازی تیم Angular
Hello! I work for the Angular core team on internationalization (i18n) and we are trying to get a better idea of the things that we need to improve/focus on in Angular regarding i18n. I created this survey: https://goo.gl/forms/0BIXXVP58RqbzzBM2 If you care about internationalization, please take 5mn to do it! Thanks!
شرکت در نظرسنجی امکانات بومی سازی تیم Angular
مطالب
طراحی شیء گرا: OO Design Heuristics - قسمت دوم

در قسمت اول با مفاهیم اولیه Class و Object آشنا شدیم.

Messages and Methods

Objectها باید مانند ماشین‌هایی تلقی شوند که عملیات موجود در واسط عمومی خود را برای افرادی که درخواست مناسبی ارسال کنند، اجرا خواهند کرد. با توجه به اینکه یک object از استفاده کننده خود مستقل است و وابستگی به او ندارد و همچنین توجه به ساختار نحوی (syntax) برخی از زبان‌های شیء گرای جدید، عبارت «sending a message» برای توصیف اجرای رفتاری از مجموعه رفتارهای object، استفاده میشود.
به محض اینکه پیغامی (Message) به سمت object ارسال شود، ابتدا باید تصمیم بگیرد که این پیغام ارسالی را درک می‌کند. فرض کنیم این پیغام قابل درک است. در این صورت object مورد نظر، همزمان با نگاشت پیغام به یک فراخوانی تابع (function call)، خود را به صورت ضمنی به عنوان اولین آرگومان ارسال می‌کند. تصمیم گرفتن در رابطه با قابل درک بودن یک پیغام، در زبان‌های مفسری در زمان اجرا و در زبان‌های کامپایلری در زمان کامپایل، انجام میگرد. 
نام (یا prototype) رفتار یک وهله، Message (پیغام) نامیده می‌شود. بسیاری از زبان‌های شیء گرا مفهموم Overloaded Functions Or Operators را پشتیبانی می‌کنند. در این صورت می‌توان در سیستم دو تابعی داشت که با نام یکسان، یا انواع مختلف آرگومان (intraclass overloading) داشته باشند یا در کلاس‌های مختلفی (interclass overloading) قرار گیرند. 
ممکن است کلاس ساعت زنگدار، دو پیغام set_time که یکی از آنها با دو آرگومان از نوع عدد صحیح و دیگری یک آرگومان رشته‌ای است داشته باشد.

void AlarmClock::set_time(int hours, int minutes); 

void AlarmClock::set_time(String time);

در مقابل، کلاس ساعت زنگدار و کلاس ساعت مچی هر دو messageای به نام set_time با دو آرگومان از نوع عدد صحیح دارند.

void AlarmClock::set_time(int hours, int minutes); 

void Watch::set_time(int hours, int minutes);

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

در برخی از زبان‌ها و یا سیستم‌ها، اطلاعات دیگری مانند: انواع استثناءهایی که از سمت پیغام پرتاب می‌شوند تا اطلاعات همزمانی (پیغام به صورت همزمان است یا ناهمزمان) را برای استفاده کننده مهیا کنند. از طرفی پیاده سازی کنندگان یک کلاس باید از پیاده سازی پیغام آگاه باشند. جزئیات پیاده سازی یک پیغام -کدی که پیغام را پیاده سازی می‌کند- Method (متد) نامیده میشود. آنگاه که نخ (thread) کنترل درون متد باشد، برای مشخص کردن اینکه پیغام رسیده برای کدام وهله ارسال شده‌است، ارجاعی به وهله مورد نظر و به عنوان اولین آرگومان، به صورت ضمنی ارسال می‌شود. این آرگومان ضمنی، در بیشتر زبان‌ها Self Object نامیده می‌شود (در سی پلاس پلاس this object نام دارد). در نهایت، مجموعه پیغام‌هایی که یک وهله می‌تواند به آنها پاسخ دهد، Protocol (قرارداد) نام دارد.

دو پیغام خاصی که کلاس‌ها یا وهله‌ها می‌توانند به آنها پاسخ دهند، اولی که استفاده کنندگان کلاس برای ساخت وهله‌ها از آن استفاده می‌کنند، Constructor (سازنده) نام دارد. هر کلاسی می‌تواند سازنده‌های متعددی داشته باشد که هر کدام مجموعه پارامترهای مختلفی را برای مقدار دهی اولیه می‌پذیرند. دومین پیغام، عملیاتی است که وهله را قبل از حذف از سیستم، پاک سازی می‌کند. این عملیات، Destructor (تخریب کننده) نام دارد. بیشتر زبان‌های شیء گرا، برای هر کلاس تنها یک تخریب کننده دارند. این پیغام‌ها را به عنوان مکانیزم مقدار دهی اولیه و پاک سازی در پارادایم شیء گرا در نظر بگیرید.

قاعده شهودی 2.2

استفاده کنندگان از کلاس باید به واسط عمومی آن وابسته باشند، اما یک کلاس نباید به استفاده کنندگان خود، وابسته باشد. (Users of a class must be dependent on its public interface, but a class should not be dependent on its users)

منطق پشت این قاعده، یکی از شکل‌های قابلیت استفاده مجدد (resuability) می‌باشد. یک ساعت زنگدار ممکن است توسط شخصی در اتاق خواب او استفاده شود. واضح است که شخص مورد نظر به واسط عمومی ساعت زنگدار وابسته می‌باشد. به هر حال، ساعت زنگدار نباید به شخصی وابسته باشد. اگر ساعت زنگدار به شخصی وابسته باشد، بدون مهیا کردن یک شخص، نمی‌توان از آن برای ساخت یک TimeLockSafe استفاده کرد. این وابستگی‌ها برای مواقعیکه می‌خواهیم امکان این را داشته باشیم تا کلاس ساعت زنگدار را از دامین (domain) خود خارج کرده و در دامین دیگری، بدون وابستگی هایش مورد استفاده قرار دهیم، نامطلوب هستند.

شکل 2.4 The Use Of Alarm Clocks

 The Use Of Alarm Clocks قاعده شهودی 2.3

تعداد پیغام‌های موجود در قرارداد یک کلاس را کمینه سازید. (Minimize the number of messages in the protocol of a class)

چندین سال قبل، مطلبی منتشر شد که کاملا متضاد این قاعده شهودی می‌باشد. طبق آن، پیاده سازی کننده یک کلاس می‌تواند یکسری عملیات را با فرض اینکه در آینده مورد استفاده قرار گیرند، برای آن در نظر بگیرد. ایراد این کار چیست؟ اگر شما از این قاعده پیروی کنید، حتما کلاس LinkedList من، توجه شما را جلب خواهد کرد؛ این کلاس در واسط عمومی خود 4000 عملیات را دارد. فرض کنید قصد ادغام دو وهله از این کلاس را داشته باشید. در این صورت حتما فرض شما این است که عملیاتی تحت عنوان merge در این کلاس تعبیه شده است. بعد از جستجوی بین این تعداد عملیات، در نهایت این عملیات خاص را پیدا نخواهید کرد. چراکه این عملیات متأسفانه به صورت یک overloaded plus operator پیاده سازی شده است. مشکل اصلی واسط عمومی با تعداد زیادی عملیات این است که فرآیند یافتن عملیات مورد نظرمان را خیلی سخت یا حتی ناممکن خواهد کرد و مشکلی جدی برای قابلیت استفاده مجدد تلقی می‌شود.

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

قاعده شهودی 2.4

پیاده سازی یک واسط عمومی یکسان کمینه برای همه کلاس‌ها  (Implement a minimal public interface that all classes understand [e.g., operations such as copy (deep versus shallow), equality testing, pretty printing, parsing from an ASCII description, etc.].)

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

به عنوان مثال کلاس Object در دات نت به عنوان کلاس پایه ضمنی با یکسری از متدهای عمومی (برای مثال ToString)، نشان دهنده تعریف یک واسط عمومی مشترک برای همه کلاس‌ها در این فریمورک، می‌باشد.

public class Object
    {
        public Object();
        public static bool Equals(Object objA, Object objB){...}
        public static bool ReferenceEquals(Object objA, Object objB){...}
        public virtual bool Equals(Object obj){...}
        public virtual int GetHashCode(){...}
        public Type GetType(){...}
        public virtual string ToString(){...}
        protected Object MemberwiseClone(){...}
    }


قاعده شهودی 2.5 

جزئیات پیاده سازی، مانند توابع خصوصی common-code  ( توابعی که کد مشترک سایر متدهای کلاس را در بدنه خود دارند) را در واسط عمومی یک کلاس قرار ندهید.  (Do not put implementation details such as common-code private functions into the public interface of a class)

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

شکل 2.5  Example of a common-code private function

Example of a common-code private function

مثال واقعی

فرض کنید در شکل بالا، کلاس X معادل یک LinkedList کلاس، f1و f2 به عنوان توابع insert و remove و تابع f به عنوان تابع common-code که عملیات یافتن آدرس را برای درج و حذف انجام می‌دهد، می‌باشند.

قاعده شهودی 2.6

واسط عمومی کلاس را با اقلامی که یا استفاده کنندگان از کلاس توانایی استفاده از آن را نداشته و یا تمایلی به استفاده از آنها ندارند، آمیخته نکنید.  (Do not clutter the public interface of a class with items that users of that class are not able to use or are not interested in using )

 این قاعده شهودی با قاعده قبلی که با قرار دادن تابع common-code در واسط عمومی کلاس، فقط باعث در هم ریختن واسط عمومی شده بود، مرتبط می‌باشد. در برخی از زبان‌ها مانند C++‎، برای مثال این امکان وجود دارد که سازنده یک کلاس انتزاعی (abstract) را در واسط عمومی آن قرار دهید؛ حتی با وجود اینکه در زمان استفاده از آن سازنده با خطای نحوی روبرو خواهید شد. این قاعده شهودی کلی، برای کاهش این مشکلات در نظر گرفته شده است. 

مطالب دوره‌ها
توابع(Function)
برنامه نویسی تابع گرا در یک جمله یعنی نوشتن توابع در پروژه و فراخوانی آن‌ها به همراه مقدار دهی به آرگومان‌های متناظر و دریافت خروجی در صورت نیاز. در #F پارامتر‌های یک تابع با پرانتز یا کاما از هم تمیز داده نمی‌شوند بلکه باید فقط از یک فضای خالی بین آن‌ها استفاده کنید.(البته می‌تونید برای خوانایی بهتر از پرانتز استفاده کنید)
let add x y = x + y
let result = add 4 5
printfn "(add 4 5) = %i" result
همان طور که می‌بینید تابعی به نام add داریم که دارای 2 پارامتر ورودی است به نام‌های x , y که فقط توسط یک فضای خالی از هم جدا شدند. حال به مثال دیگر توجه کنید.
let add x y = x + y
let result1 = add 4 5
let result2 = add 6 7
let finalResult = add result1 result2
در مثال بالا همان تابع add 2 بار فراخوانی شده است که یک بار مقدار خروجی تابع در یک شناسه به نام result1 و یک بار مقدار خروجی با مقادیر متفاوت در شناسه به نام result2 قرار گرفت. شناسه finalResult حاصل فراخوانی تابع add با مقادیر result1 , result2 است. می‌تونیم کد بالا رو به روش مناسب‌تری باز نویسی کنیم.
let add x y = x + y
let result =add (add 4 5) (add 6 7)
در اینجا برای خوانایی بهتر کد از پرانتز برای جداسازی مقدار پارامتر‌ها استفاده کردم.

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

let cylinderVolume radius length : float =
   
   let pi = 3.14159
   length * pi * radius * radius
در مثال بالا خروجی تابع مقدار ( length * pi * radius * radius ) است و نو ع آن float می‌باشد.
یک مثال دیگر:
let sign num =
    if num > 0 then "positive"
    elif num < 0 then "negative"
    else "zero"
خروجی تابع بالا از نوع string است و مقدار آن با توجه به ورودی تابع positive یا negative یا zero خواهد بود.

تعریف پارامترهای تابع با ذکر نوع به صورت صریح
اگر هنگام تعریف توابع مایل باشید که نوع پارامترها را به صورت صریح تعیین کنید از روش زیر استفاده می‌کنیم.
let replace(str: string) =
    str.Replace("A", "a")
تعریف تابع به همراه دو پارامتر و ذکر نوع فقط برای یکی از پارامتر‌ها :
let addu1 (x : uint32) y =
    x + y

Pipe-Forward Operator
در #F روشی دیگری برای تعریف توابع وجود دارد که به pipe-Forward معروف است. فقط کافیست از اپراتور (<|) به صورت زیر استفاده کنید.
let (|>) x f = f x
کد بالا به این معنی است که تابعی  یک پارامتر ورودی به نام x دارد و این پارامتر رو به تابع مورد نظر(هر تابعی که شما هنگام استفاده تعیین کنید) تحویل می‌دهد و خروجی را بر می‌گرداند. برای مثال
let result = 0.5 |> System.Math.Cos
یا
let add x y = x + y
let result = add 6 7 |> add 4 |> add 5
در مثال بالا ابتدا حاصل جمع 7 و 6 محاسبه می‌شود و نتیجه با 4 جمع می‌شود و دوباره نتیجه با 5 جمع می‌شود تا حاصل نهایی در result قرار گیرد. به نظر اکثر برنامه نویسان #F این روش نسبت به روش‌های قبلی خواناتر است. این روش همچنین مزایای دیگری نیز دارد که در مبحث Partial Function‌ها بحث خواهیم کرد.

Partial Fucntion Or Application
partial function به این معنی است که در هنگام فراخوانی یک تابع نیاز نیست که به تمام آرگومان‌های مورد نیاز مقدار اختصاص دهیم. برای نمونه در مثال بالا تابع add نیاز به 2 آرگومان ورودی داشت در حالی که فقط یک مقدار به آن پاس داده شد.
let result = add 6 7 |> add 4
دلیل برخورد #F با این مسئله این است که #F توابع رو به شکل مقدار در نظر می‌گیرد و اگر تمام مقادیر مورد نیاز یک تابع در هنگام فراخوانی تحویل داده نشود، از مقدار برگشت داده شده فراخوانی تابع قبلی استفاده خواهد کرد. البته این مورد همیشه خوشایند نیست. اما می‌تونیم با استفاده از پرانتز ر هنگام تعریف توابع مشخص کنیم که دقیقا نیاز به چند تا مقدار ورودی برای توابع داریم.
let sub (a, b) = a - b
let subFour = sub 4
کد بالا کامپایل نخواهد شد و خطای زیر رو مشاهده خواهید کرد.
prog.fs(15,19): error: FS0001: This expression has type
int
but is here used with type
'a * 'b
توابع بازگشتی
در مورد ماهیت توابع بازگشتی نیاز به توضیح نیست فقط در مورد نوع پیاده سازی اون در #F توضیح خواهم داد. برای تعریف توابع به صورت بازگشتی کافیست از کلمه rec بعد از let استفاده کنیم(زمانی که قصد فراخوانی تابع رو در خود تابع داشته باشیم). مثال پایین به خوبی مسئله را روشن خواهد کرد.(پیاده سازی تابع فیبو ناچی)
let rec fib x =
match x with
| 1 -> 1
| 2 -> 1
| x -> fib (x - 1) + fib (x - 2)
printfn "(fib 2) = %i" (fib 2) printfn "(fib 6) = %i" (fib 6) printfn "(fib 11) = %i" (fib 11) 
*درباره الگوی Matching در فصل بعد به صورت کامل توضیح خواهم داد.
خروجی برای مثال بالا به صورت خواهد شد.
(fib 2) = 1
(fib 6) = 8
(fib 11) = 89
توابع بازگشتی دو طرفه
گاهی اوقات توابع به صورت دوطرفه بازگشتی می‌شوند. یعنی فراخوانی توابع به صورت چرخشی انجام می‌شود. (فراخوانی یک تابع در تابع دیگر و بالعکس). به مثال زیر دقت کنید.
let rec Even x =
   if x = 0 then true 
   else Odd (x - 1)
and Odd x =
   if x = 1 then true 
   else Even (x - 1)
کاملا واضح است در تابع Even فراخوانی تابع Odd انجام می‌شود و در تابع Odd فراخوانی تابع Even. به این حالت mutual recursive می‌گویند.
ترکیب توابع
let firstFunction x = x + 1
let secondFunction x = x * 2
let newFunction = firstFunction >> secondFunction
let result = newFunction 100
در مثال بالا دو تابع به نام‌های firstFunction  و secondFunction داریم. بااستفاده از (<<) دو تابع را با هم ترکیب می‌کنیم. خروجی بدین صورت محاسبه می‌شود که ابتدا تابع firstFucntion مقدار x را محاسبه می‌کند و حاصل به تابع secondFucntion پاس داده می‌شود. در نهایت یک تابع جدید به نام newFunction خواهیم داشت که مقدار نهایی محاسبه خواهد شد. خروجی مثال بالا 202 است.

توابع تودرتو
در #F امکان تعریف توابع تودرتو وجود دارد. بعنی می‌تونیم یک تابع را در یک تابع دیگر تعریف کنیم. فقط نکته مهم در امر استفاده از توابع به این شکل این است که توابع تودرتو فقط در همون تابعی که تعریف می‌شوند قایل استفاده هستند و محدوده این توابع در خود همون تابع است.
let sumOfDivisors n =
    let rec loop current max acc =
//شروع تابع داخلی
 if current > max then acc else if n % current = 0 then loop (current + 1) max (acc + current) else loop (current + 1) max acc
//ادامه بدنه تابع اصلی
 let start = 2 let max = n / 2 (* largest factor, apart from n, cannot be > n / 2 *) let minSum = 1 + n (* 1 and n are already factors of n *) loop start max minSum printfn "%d" (sumOfDivisors 10)
در مثال بالا یک تابع تعریف کرده ایم به نام sumOfDivisors. در داخل این تابع یک تابع دیگر به نام loop داریم که از نوع بازگشتی است(به دلیل وجود rec بعد از let). بدنه تابع داخلی به صورت زیر است:
  if current > max then
            acc
        else
            if n % current = 0 then
                loop (current + 1) max (acc + current)
            else
                loop (current + 1) max acc
خروجی مثال بالا برای ورودی 10 عدد 18 می‌باشد. مجموع مقصوم علیه‌های عدد 10 (1 + 2 + 5 + 10 ).

آیا می‌توان توابع را Overload کرد؟
در #F امکان overloading برای یک تابع وجود ندارد. ولی متدها را می‌توان overload  کرد.(متد‌ها در فصل شی گرایی توضیح داده می‌شود).

do keyword
زمانی که قصد اجرای یک کد را بدون تعریف یک تابع داشته باشیم باید از do  استفاده کنیم. همچنین از do در انجام برخی عملیات پیش فرض در کلاس‌ها زیاد استفاده می‌کنیم.(در فصل شی گرایی با این مورد آشنا خواهید شد).
open System
open System.Windows.Forms

let form1 = new Form()
form1.Text <- "XYZ"

[<STAThread>]
do
   Application.Run(form1)

اشتراک‌ها
CSnakes؛ کتابخانه‌ای برای جایگذاری کدهای پایتون در برنامه‌های دات‌نت
CSnakes - a tool for embedding Python code into .NET projects

CSnakes is a .NET Source Generator and Runtime that you can use to embed Python code and libraries into your C#.NET Solution at a performant, low-level without the need for REST, HTTP, or Microservices.
CSnakes؛ کتابخانه‌ای برای جایگذاری کدهای پایتون در برنامه‌های دات‌نت
مطالب
بررسی Source Generators در #C - قسمت چهارم - روش دسترسی به تنظیمات برنامه
در حین توسعه‌ی Source Generators، نیاز می‌شود تا بتوان تنظیماتی را از استفاده کننده دریافت کرد؛ برای مثال تعیین فضای نام ویژه‌ای، فعال و غیرفعال کردن قابلیتی و یا حتی دریافت فایل‌های تکمیلی. این تنظیمات سفارشی از طریق تعریف آن‌ها در فایل‌های csproj. و خواص MSBuild قابل دسترسی هستند که روش کار با آن‌ها را در ادامه مرور خواهیم کرد.


روش تعریف خواص سفارشی MSBuild در پروژه‌ی Source Generator

در مثال همین سری، به پوشه‌ی NotifyPropertyChangedGenerator مراجعه کرده و فایل جدید NotifyPropertyChangedGenerator.props را با محتوای زیر به آن اضافه می‌کنیم:
<Project>
    <ItemGroup>
        <CompilerVisibleProperty Include="SourceGenerator_CustomRootNamespace"/>        
    </ItemGroup>
</Project>
هدف این است که خاصیت جدیدی به نام SourceGenerator_CustomRootNamespace، توسط استفاده کننده در فایل csproj. برنامه‌، قابل تعریف شود. علت قرار دادن این تعاریف در یک فایل props. مجزا، آماده کردن این پروژه جهت ارائه‌ی به صورت یک بسته‌ی نیوگت است که نیاز به تنظیمات ذیل را نیز دارد:
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <GeneratePackageOnBuild>true</GeneratePackageOnBuild> <!-- Generates a package at build -->
        <IncludeBuildOutput>false</IncludeBuildOutput> <!-- Do not include the generator as a lib dependency -->
    </PropertyGroup>

    <ItemGroup>
        <!-- Package the generator in the analyzer directory of the nuget package -->
        <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false"/>

        <!-- Package the props file -->
        <None Include="NotifyPropertyChangedGenerator.props" Pack="true" PackagePath="build" Visible="true"/>
    </ItemGroup>
</Project>
با این تنظیمات، فایل props. یاد شده، در پوشه‌ی build بسته‌ی نیوگت قرار می‌گیرد و با سیستم build پروژه‌ی استفاده کننده یکی می‌شود. همچنین باید در تنظیمات دیگری، مقدار PackagePath را به analyzers/dotnet/cs و IncludeBuildOutput را به false تنظیم کرد تا تولید کننده‌ی کد، در پوشه‌ی مخصوص آنالایزرها قرار گیرد و نه در جای دیگری. اگر این موارد رعایت نشوند، بسته‌ی نیوگت نهایی، سبب تولید کدی نخواهد شد و کار نمی‌کند.

یک نکته‌ی مهم! اگر بخواهیم مستقیما از پروژه‌ی source generator خود مثلا در پروژه‌ی NotifyPropertyChangedGenerator.Demo این سری همانند قبل استفاده کنیم، تنظیمات ذکر شده‌ی در فایل props. فوق، در آن قابل دسترسی نخواهند بود و با پروسه‌ی build یکی نمی‌شوند. تنظیماتی که تا اینجا ذکر شدند، فقط مخصوص بسته‌ی نیوگت نهایی است. برای استفاده‌ی مستقیم از آن‌ها در پروژه‌ی Demo، نیاز است یکبار دیگر محتویات props. تولید کننده‌ی کد را داخل فایل csproj. پروژه‌ی Demo، تعریف کرد. یا می‌توان از روش استفاده از فایل ویژه‌ی Directory.Build.props و قابلیت‌های ارث‌بری آن استفاده کرد. یعنی یک فایل Directory.Build.props را در بالاترین سطح ممکن قرار داد و CompilerVisiblePropertyها را در آن تعریف کرد تا در تمام پروژه‌های برنامه قابل دسترسی شوند.


روش تعریف خواص سفارشی MSBuild در پروژه‌ی استفاده کننده از Source Generator

در مثال این سری چون از بسته‌ی نیوگت تولید کننده‌ی کد استفاده نمی‌کنیم، نیاز است خاصیت سفارشی تعریف شده را یکبار دیگر داخل فایل csproj. پروژه‌ی Demo تعریف کنیم. پس از آن می‌توان این خاصیت را در قسمت PropertyGroup مقدار دهی کرد:
<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <SourceGenerator_CustomRootNamespace>ThisIsTest</SourceGenerator_CustomRootNamespace>
    </PropertyGroup>


    <ItemGroup>
        <CompilerVisibleProperty Include="SourceGenerator_CustomRootNamespace"/>
    </ItemGroup>
</Project>
البته بدیهی است اگر از بسته‌ی نیوگت تولید کننده‌ی کد استفاده شود، نیازی به ذکر قسمت CompilerVisibleProperty نخواهد بود و آن‌را به صورت خودکار از فایل props. به همراه بسته‌ی نیوگت دریافت می‌کند.


روش دسترسی به مقدار خاصیت سفارشی MSBuild در پروژه‌ی Source Generator

پس از این مقدمات، خواص عمومی MSBuild از طریق خاصیت AnalyzerConfigOptions.GlobalOptions و متد TryGetValue آن با فرمول زیر قابل دسترسی هستند. قسمت build_property ثابت بوده و جزو موارد توکار MSBuild است:
internal static class SourceGeneratorContextExtensions
{
    public static string GetMSBuildProperty(
        this GeneratorExecutionContext context,
        string property,
        string defaultValue = "")
    {
        return !context.AnalyzerConfigOptions.GlobalOptions.TryGetValue($"build_property.{property}", out var value)
            ? defaultValue
            : value;
    }
}
برای نمونه روش دسترسی به خاصیت SourceGenerator_CustomRootNamespace تعریف شده، در متد Execute به صورت زیر است:
[Generator]
public class NotifyPropertyChangedGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
    }

    public void Execute(GeneratorExecutionContext context)
    {
        var customRootNamespace = context.GetMSBuildProperty("SourceGenerator_CustomRootNamespace", "Test");


معرفی خاصیت ویژه‌ی AdditionalFiles

تا اینجا روش تعریف یک خاصیت جدید MSBuild و روش دسترسی به آن‌را بررسی کردیم. خاصیت توکاری به نام AdditionalFiles نیز در MSBuild تعریف شده‌است که در پروژه‌های Source Generator جهت دسترسی به فایل‌ها و محتوای آن‌ها قابل استفاده است. برای نمونه می‌توان در فایل csproj. پروژه‌ی Demo تعریف زیر را ارائه کرد:
<Project Sdk="Microsoft.NET.Sdk">
    <ItemGroup>
        <AdditionalFiles Include="file1.txt" Visible="false"/> 
    </ItemGroup>
</Project>
که در اینجا file1.txt، مسیر فایلی است که در پروژه‌ی Source Generator از طریق خاصیت context.AdditionalFiles قابل دسترسی است. AdditionalFiles یک آرایه‌است؛ یعنی می‌توان در پروژه‌ی Demo، چندین AdditionalFiles را تعریف و استفاده کرد. هر AdditionalText که معرف اجزای AdditionalFiles است، به همراه مسیر فایل معرفی شده و همچنین متد GetText است.
خاصیت Visible در اینجا مشخص می‌کند که آیا file1.txt در IDE، در کنار لیست سایر فایل‌ها نمایش داده شود یا خیر.


امکان تعریف خواص سفارشی بر روی AdditionalFiles

فرض کنید علاقمندیم خاصیت ویژه‌ای را به AdditionalFiles اضافه کنیم؛ برای مثال به نام SourceGenerator_EnableLogging مانند مثال زیر:
<Project Sdk="Microsoft.NET.Sdk">
    <ItemGroup>
        <AdditionalFiles Include="file2.txt" SourceGenerator_EnableLogging="true" Visible="false"/> 
    </ItemGroup>
</Project>
روش انجام اینکار به صورت زیر است:
الف) فایل NotifyPropertyChangedGenerator.props تعریف شده را به صورت زیر تکمیل می‌کنیم:
<Project>
    <ItemGroup>
        <CompilerVisibleProperty Include="SourceGenerator_CustomRootNamespace"/>
        
        <CompilerVisibleProperty Include="SourceGenerator_EnableLogging"/>
        <CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="SourceGenerator_EnableLogging"/>
    </ItemGroup>
</Project>
یعنی در اینجا هم می‌توان خاصیت SourceGenerator_EnableLogging را به صورت سراسری در قسمت PropertyGroupهای استفاده کننده تعریف کرد و همچنین توسط CompilerVisibleItemMetadata و MetadataName آن، آن‌‌را به AdditionalFiles نیز انتساب داد. برای مثال اگر AdditionalFiles ای به همراه ویژگی SourceGenerator_EnableLogging نبود، اگر مقدار سراسری استفاده شود.

ب) در فایل csproj. پروژه‌ی Demo، از این خواص و متادیتاها استفاده می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <SourceGenerator_EnableLogging>true</SourceGenerator_EnableLogging>
    </PropertyGroup>

    <ItemGroup>
        <CompilerVisibleProperty Include="SourceGenerator_EnableLogging"/>
        <CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="SourceGenerator_EnableLogging"/>

        <AdditionalFiles Include="file1.txt" Visible="false"/>  <!-- logging will be controlled by default, or global value -->
        <AdditionalFiles Include="file2.txt" SourceGenerator_EnableLogging="true" Visible="false"/>  <!-- always enable logging for this file -->
        <AdditionalFiles Include="file3.txt" SourceGenerator_EnableLogging="false" Visible="false"/> <!-- never enable logging for this file -->
    </ItemGroup>
</Project>
همانطور که عنوان شد چون از بسته‌ی نیوگت تولید کننده‌ی کد استفاده نمی‌کنیم، نیاز است خواص جدید تعریف شده را یکبار دیگر هم در اینجا تکرار کنیم. پس از آن امکان مقدار دهی SourceGenerator_EnableLogging میسر می‌شود.

ج) برای خواندن این خواص در پروژه‌ی Source generator به صورت زیر عمل می‌شود:
internal static class SourceGeneratorContextExtensions
{
    public static string GetAdditionalFilesOption(
        this GeneratorExecutionContext context,
        AdditionalText additionalText,
        string property,
        string defaultValue = "")
    {
        return !context.AnalyzerConfigOptions.GetOptions(additionalText)
            .TryGetValue($"build_metadata.AdditionalFiles.{property}", out var value)
            ? defaultValue
            : value;
    }
}
- در اینجا build_metadata.AdditionalFiles ثابت بوده و جزو تنظیمات MSBuild است.
- روش تامین AdditionalText این متد را نیز در مثال زیر مشاهده می‌کنید که حاصل ایجاد حلقه‌ای بر روی context.AdditionalFiles‌های دریافتی است:
[Generator]
public class NotifyPropertyChangedGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
    }

    public void Execute(GeneratorExecutionContext context)
    {
        var customRootNamespace = context.GetMSBuildProperty("SourceGenerator_CustomRootNamespace", "Test");

        var globalLoggingSwitch = context.GetMSBuildProperty("SourceGenerator_EnableLogging", "false");
        var emitLoggingGlobal = globalLoggingSwitch.Equals("true", StringComparison.OrdinalIgnoreCase);

        foreach (var file in context.AdditionalFiles)
        {
            var perFileLoggingSwitch = context.GetAdditionalFilesOption(file, "SourceGenerator_EnableLogging");
            var emitLogging = string.IsNullOrWhiteSpace(perFileLoggingSwitch)
                ? emitLoggingGlobal // allow the user to override the global logging on a per-file basis
                : perFileLoggingSwitch.Equals("true", StringComparison.OrdinalIgnoreCase);

            // add the source with or without logging...
        }
در این مثال یکبار به مقدار سراسری SourceGenerator_EnableLogging ارجاع شده که در قسمت PropertyGroupها تعریف شده و سپس درون حلقه، به متادیتای تعریف شده‌ی بر روی AdditionalFiles اشاره می‌کند.
مطالب
رشته ها و پردازش متن در دات نت به زبان ساده
رشته، مجموعه‌ای از کاراکترهاست که پشت سرهم، در مکانی از حافظه قرار گرفته‌اند. هر کاراکتر حاوی یک شماره سریال در جدول یونیکد هست. به طور پیش فرض دات نت برای هر کاراکتر (نوع داده char) شانزده بیت در نظر گرفته است که برای 65536 کاراکتر کافی است.
برای نگهداری از رشته‌ها و انجام عملیات بر روی آنها در دات نت از نوع system.string استفاده می‌کنیم:
string greeting = "Hello, C#";

که در این حالت مجموعه‌ای از کاراکترها را ایجاد خواهد کرد:

اتفاقاتی که در داخل کلاس string رخ می‌دهد بسیار ساده است و ما را از تعریف []char بی‌نیاز می‌کند تا مجبور نشویم خانه‌های  آرایه را به ترتیب پر کنیم. از معایب استفاده از آرایه char میتوان موارد زیر را برشمارد:
  1. خانه‌های آن یک ضرب پر نمیشوند بلکه به ترتیب، خانه به خانه پر می‌شوند.
  2. قبل از انتساب متن باید باید از طول متن مطمئن شویم تا بتوانیم تعداد خانه‌ها را بر اساس آن ایجاد کنیم.
  3. همه عملیات آرایه‌ها از پر کردن ابتدای کار گرفته تا هر عملی، نیاز است به صورت دستی صورت بگیرد و تعداد خطوط کد برای هر کاری هم بالا می‌رود.
البته استفاده از string هم راه حل نهایی برای کار با متون نیست. در انتهای این مطلب مورد دیگری را نیز بررسی خواهیم کرد. از ویژگی دیگر رشته‌ها این است که آن‌ها شباهت زیادی به آرایه‌ای از کاراکتر‌ها دارند؛ ولی اصلا شبیه آن‌ها نیستند و نمی‌توانید به صورت یک آرایه آن‌ها را مقداردهی کنید. البته کلاس string امکاناتی را با استفاده از indexer [] مهیا کرده است که میتوانید بر اساس اندیس‌ها به کاراکترها به صورت جداگانه دسترسی داشته باشید ولی نمی‌توانید آن‌ها را مقدار دهی کنید. این اندیس‌ها از 0 تا طول آن length-1 ادامه دارند.
string str = "abcde";
char ch = str[1]; // ch == 'b'
str[1] = 'a'; // Compilation error!
ch = str[50]; // IndexOutOfRangeException
همانطور که میدانیم برای مقداردهی رشته‌ها از علامت‌های نقل قول "" استفاده میکنیم که باعث میشود اگر بخواهیم علامت " را در رشته‌ها داشته باشیم نتوانیم. برای حل این مشکل از علامت \ استفاده میکنیم که البته باعث استفاده از بعضی کاراکترهای خاص دیگر هم می‌شود:
string a="Hello \"C#\"";
string b="Hello \r\n C#"; //مساوی با اینتر
string c="C:\\a.jpg"; //چاپ خود علامت  \ -مسیردهی
البته اگر از علامت @ در قبل از رشته استفاده شود علامت \ بی اثر خواهد شد.
string c=@"C:\a.jpg";// == "C:\\a.jpg"

مقداردهی رشته‌ها و پایدار (تغییر ناپذیر) بودن آنها Immutable
رشته‌ها ساختاری پایدار هستند؛ به این معنی که به صورت reference مقداردهی می‌شوند. موقعی که شما مقداری را به یک رشته انتساب می‌دهید، مقدار متغیر در  String pool یا لینک در Heap ذخیره می‌شوند و اگر همین متغیر را به یک متغیر دیگر انتساب دهیم، متغیر جدید مقدار آن را دیگر در حافظه پویا (داینامیک) Heap به عنوان مقدار جدید ذخیره نخواهد کرد؛ بلکه تنها یک pointer خواهد بود که به آدرس حافظه متغیر اولی اشاره می‌کند. به مثال زیر دقت کنید. متغیر source مقدار some source را ذخیره می‌کند و بعد همین متغیر، به متغیر assigned انتساب داده میشود؛ ولی مقداری جابجا نمی‌شود. بلکه متغیر assign به آدرسی در حافظه اشاره می‌کند که متغیر source اشاره می‌کند. هرگاه که در یکی از متغیرها، تغییری رخ دهد، همان متغیری که تغییر کرده است، به آدرس جدید با محتوای تغییر داده شده اشاره می‌کند.
string source = "Some source";
string assigned = source;

این ویژگی نوع reference فقط برای ساختارهای Immutable به معنی پایدار رخ می‌دهد و نه برای ساختار‌های ناپایدار (تغییر پذیر)  mutable؛ به این خاطر که آن‌ها مقادیرشان را مستقیما تغییر میدهند و اشاره‌ای در حافظه صورت نمی‌گیرد. 
string hel = "Hel";
string hello = "Hello";
string copy = hel + "lo";

string hello = "Hello";
string same = "Hello";

برای اطلاعات بیشتر در این زمینه این لینک را مطالعه نمایید.


مقایسه رشته‌ها
برای مقایسه دو رشته میتوان از علامت == یا از متد Equals استفاده نماییم. در این حالت به خاطر اینکه کد حروف کوچک و بزرگ متفاوت است، مقایسه حروف هم متفاوت خواهد بود. برای اینکه حروف کوچک و بزرگ تاثیری بر مقایسه ما نگذارند و #c را با #C برابر بدانند باید از متد Equals به شکل زیر استفاده کنیم:
Console.WriteLine(word1.Equals(word2,
    StringComparison.CurrentCultureIgnoreCase));
برای اینکه بزرگی و کوچکی اعداد را مشخص کنیم از علامت‌های < و > استفاده میکنیم ولی برای رشته‌ها از متد CompareTo بهره می‌بریم که چینش قرارگیری آن‌ها را بر اساس حروف الفبا مقایسه می‌کند و سه عدد، می‌تواند خروجی آن باشند. اگر 0 باشد یعنی برابر هستند، اگر -1 باشد رشته اولی قبل از رشته دومی است و اگر 1 باشد رشته دومی قبل از رشته اولی است.
string score = "sCore";
string scary = "scary";
 
Console.WriteLine(score.CompareTo(scary));
Console.WriteLine(scary.CompareTo(score));
Console.WriteLine(scary.CompareTo(scary));
 
// Console output:
// 1
// -1
// 0
 اینبار هم برای اینکه حروف کوچک و بزرگ، دخالتی در کار نداشته باشند، میتوانید از داده شمارشی StringComparison در متد ایستای (string.Compare(s1,s2,StringComparison استفاده نمایید؛ یا از نوع داده‌ای boolean برای تعیین نوع مقایسه استفاده کنید.
string alpha = "alpha";
string score1 = "sCorE";
string score2 = "score";
 
Console.WriteLine(string.Compare(alpha, score1, false));
Console.WriteLine(string.Compare(score1, score2, false));
Console.WriteLine(string.Compare(score1, score2, true));
Console.WriteLine(string.Compare(score1, score2,
    StringComparison.CurrentCultureIgnoreCase));
// Console output:
// -1
// 1
// 0
// 0
نکته : برای مقایسه برابری  دو رشته از متد Equals یا == استفاده کنید و فقط برای تعیین کوچک یا بزرگ بودن از compare‌ها استفاده نمایید. دلیل آن هم این است که برای مقایسه از فرهنگ culture فعلی سیستم استفاده میشود و نظم جدول یونیکد را رعایت نمی‌کنند و ممکن است بعضی رشته‌های نابرابر با یکدیگر برابر باشند. برای مثال در زبان آلمانی دو رشته "SS" و "ß " با یکدیگر برابر هستند.

عبارات با قاعده Regular Expression
این عبارات الگوهایی هستند که قرار است عبارات مشابه الگویی را در رشته‌ها پیدا کنند. برای مثال الگوی +[A-Z0-9] مشخص می‌کند که رشته مورد نظر نباید خالی باشد و حداقل با یکی از حروف بزرگ یا اعداد پرشده باشد. این الگوها میتوانند برای واکشی داده‌ها یا قالب‌های خاص در رشته‌ها به کار بروند. برای مثال شماره تماس‌ها ، پست الکترونیکی و ...
در اینجا میتواند نحوه‌ی الگوسازی را بیاموزید. کد زیر بر اساس یک الگو، شماره تماس‌های مورد نظر را یافته و البته با فیلتر گذاری آن‌ها را نمایش می‌دهد:
string doc = "Smith's number: 0898880022\nFranky can be " +
    "found at 0888445566.\nSteven's mobile number: 0887654321";
string replacedDoc = Regex.Replace(
    doc, "(08)[0-9]{8}", "$1********");
Console.WriteLine(replacedDoc);
// Console output:
// Smith's number: 08********
// Franky can be found at 08********.
// Steven' mobile number: 08********
سه شماره تماس در رشته‌ی بالا با الگوی ما همخوانی دارند که بعد با استفاده از متد replace در شی Regex عبارات دلخواه خودمان را جایگزین شماره تماس‌ها خواهیم کرد. الگوی بالا شماره تماس‌هایی را میابد که با 08 آغاز شده‌اند و بعد از آن 8 عدد دیگر از 0 تا 9 قرار گرفته‌اند. بعد از اینکه متن مطابق الگو یافت شد، ما آن را با الگوی ********1$ جایگزین می‌کنیم که علامت $ یک placeholder برای یک گروه است. هر عبارت () در عبارات با قاعده یک گروه حساب میشود و اولین پرانتر 1$ و دومین پرانتز یا گروه میشود 2$ که در عبارت بالا (08) میشود 1$ و به جای مابقی الگو، 8 علامت ستاره نمایش داده میشود.

اتصال رشته‌ها در Loop
برای اتصال رشته‌ها ما از علامت + یا متد ایستای string.concat استفاده می‌کنیم ولی استفاده‌ی از آن در داخل یک حلقه باعث کاهش کارآیی برنامه خواهد شد. برای همین بیایید ببینم در حین اتتقال رشته‌ها در حافظه چه اتفاقی رخ میدهد. ما در اینجا دو رشته str1 و str2 داریم که عبارات "super" و "star" را نگه داری می‌کنند و در واقع دو متغیر هستند که به حافظه‌ی پویای Heap اشاره می‌کنند. اگر این دو را با هم جمع کنیم و نتیجه را در متغیر result قرار دهیم، سه متغیر میشوند که هر کدام به حافظه‌ای جداگانه در heap اشاره می‌کنند. در واقع برای این اتصال، قسمت جدیدی از حافظه تخصصیص داده شده و مقدار جدید در آن نشسته‌است. در این حالت یک متغیر جدید ساخته شد که به آدرس آن اشاره می‌کند. کل این فرآیند یک فرآیند کاملا زمانبر است که با تکرار این عمل موجب از دست دادن کارآیی برنامه می‌شود؛ به خصوص اگر در یک حلقه این کار صورت بگیرد.
سیستم دات نت همانطور که میدانید شامل GC یا سیستم خودکار پاکسازی حافظه است که برنامه نویس را از dispose کردن بسیاری از اشیاء بی نیاز می‌کند. موقعی‌که متغیری به قسمتی از حافظه اشاره می‌کند که دیگر بلا استفاده است، سیستم GC به صورت خودکار آنها را پاکسازی می‌کند که این عمل زمان بر هم خودش موجب کاهش کارآیی می‌شود. همچنین انتقال رشته‌ها از یک مکان حافظه به مکانی دیگر، باز خودش یک فرآیند زمانبر است؛ به خصوص اگر رشته مورد نظر طولانی هم باشد.
مثال عملی: در تکه کد زیر قصد داریم اعداد 1 تا 20000 را در یک رشته الحاق کنیم:
 DateTime dt = DateTime.Now;
            string s = "";
        for (int index = 1; index <= 20000; index++)
        {
            s += index.ToString();
        }
            Console.WriteLine(s);
            Console.WriteLine(dt);
            Console.WriteLine(DateTime.Now);
            Console.ReadKey();
کد بالا تاز زمان نمایش کامل، بسته به قدرت سیستم ممکن است یکی دو ثانیه طول بکشد. حالا عدد را به 200000 تغییر دهید (یک صفر اضافه تر). برنامه را اجرا کنید و مجددا تست بزنید. در این حالت چند دقیقه ای بسته به قدرت سیستم زمان خواهد برد؛ مثلا دو دقیقه یا سه دقیقه یا کمتر و بیشتر.
عملیاتی که در حافظه صورت میگیرد این چند گام را طی میکند:
  • قسمتی از حافظه به طور موقت برای این دور جدید حلقه، گرفته میشود که به آن بافر میگوییم.
  • رشته قبلی به بافر انتقال میابد که بسته به مقدار آن زمان بر و کند است؛ 5 کیلو یا 5 مگابایت یا 50 مگابایت و ...
  • شماره تولید شده جدید به بافر چسبانده میشود.
  • بافر به یک رشته تبدیل میشود وجایی برای خود در حافظه Heap میگیرد.
  • حافظه رشته قدیمی و بافر دیگر بلا استفاده شده‌اند و توسط GC پاکسازی میشوند که ممکن است عملیاتی زمان بر باشد.

String Builder
این کلاس ناپایدار و تغییر پذیر است. به کد و شکل زیر دقت کنید:
string declared = "Intern pool";
string built = new StringBuilder("Intern pool").ToString();

این کلاس دیگر مشکل الحاق رشته‌ها یا دیگر عملیات پردازشی را ندارد. بیایید مثال قبل را برای این کلاس هم بررسی نماییم:
 StringBuilder sb = new StringBuilder();
      sb.Append("Numbers: ");

            DateTime dt = DateTime.Now;
        for (int index = 1; index <= 200000; index++)
        {
            sb.Append(index);
        }
            Console.WriteLine(sb.ToString());
            Console.WriteLine(dt);
            Console.WriteLine(DateTime.Now);
            Console.ReadKey();
اکنون همین عملیات چند دقیقه‌ای قبل، در زمانی کمتر، مثلا دو ثانیه انجام میشود.
حال این سوال پیش می‌آید مگر کلاس stringbuilder چه میکند که زمان پردازش آن قدر کوتاه است؟
همانطور که گفتیم این کلاس mutable یا تغییر پذیر است و برای انجام عملیات‌های ویرایشی نیازی به ایجاد شیء جدید در حافظه ندارد؛ در نتیجه باعث کاهش انتقال غیرضروری داده‌ها برای عملیات پایه‌ای چون الحاق رشته‌ها میگردد.
stringbuilder شامل یک بافر با ظرفیتی مشخص است (به طور پیش فرض 16 کاراکتر). این کلاس آرایه‌هایی از کاراکترها را پیاده سازی میکند که برای عملیات و پردازش‌هایش  از یک رابط کاربرپسند برای برنامه نویسان استفاده می‌کند. اگر تعداد کاراکترها کمتر از 16 باشد مثلا 5 ، فقط 5 خانه آرایه استفاده میشود و مابقی خانه‌ها خالی میماند و با اضافه شدن یک کاراکتر جدید، دیگر شیء جدیدی در حافظه درست نمی‌شود؛ بلکه در خانه ششم قرار می‌گیرد و اگر تعداد کاراکترهایی که اضافه می‌شوند باعث شود از 16 کاراکتر رد شود، مقدار خانه‌ها دو برابر میشوند؛ هر چند این عملیات دو برابر شدن resizing عملیاتی کند است ولی این اتفاق به ندرت رخ می‌دهد.
کد زیر یک آرایه 15 کاراکتری ایجاد می‌کند و عبارت #Hello C را در آن قرار می‌دهد.
StringBuilder sb = new StringBuilder(15);
sb.Append("Hello, C#!");

در شکل بالا خانه هایی خالی مانده است Unused و  جا برای کاراکترهای جدید به اندازه خانه‌های unused هست و اگر بیشتر شود همانطور که گفتیم تعداد خانه‌ها 2 برابر می‌شوند که در اینجا میشود 30.

استفاده از متد ایستای string.Format
از این متد برای نوشتن یک متن به صورت قالب و سپس جایگزینی مقادیر استفاده می‌شود:
DateTime date = DateTime.Now;
string name = "David Scott";
string task = "Introduction to C# book";
string location = "his office";
 
string formattedText = String.Format(
    "Today is {0:MM/dd/yyyy} and {1} is working on {2} in {3}.",
    date, name, task, location);
Console.WriteLine(formattedText);
در کد بالا ابتدا ساختار قرار گرفتن تاریخ را بر اساس الگو بین {} مشخص می‌کنیم و متغیر date در آن قرار می‌گیرد و سپس برای {1},{2},{3} به ترتیب قرار گیری آن‌ها متغیرهای name,last,location قرار میگیرند.
از ()ToString. هم می‌توان برای فرمت بندی خروجی استفاده کرد؛ مثل همین عبارت MM/dd/yyyy در خروجی نوع داده تاریخ و زمان.
نظرات مطالب
کار با کلیدهای اصلی و خارجی در EF Code first
من 3تا جدول زیر رو در بانک ساختم :

و کلاسها به صورت زیر تعریف کردم:
 public class Tb1 
    {
        public Tb1()
        {
            ListTb2 = new List<Tb2>();
        }
        public int Id { get; set; }
        public string NameTb1 { get; set; }

        public virtual ICollection<Tb2> ListTb2 { get; set; }
    }
    public class Tb2 
    {
        public Tb2()
        {
            ListTb1 = new List<Tb1>();
        }
        public int Id { get; set; }
        public string NameTb2 { get; set; }

        public virtual ICollection<Tb1> ListTb1 { get; set; }
    }
و همینطور mapping :
    public class Tb1Map : EntityTypeConfiguration<Tb1>
    {
        public Tb1Map()
        {
            this.HasKey(x => x.Id);

            this.HasMany(x => x.ListTb2)
                .WithMany(xx => xx.ListTb1)
                .Map
                (
                    x =>
                        {
                            x.MapLeftKey("Tb1Id");
                            x.MapRightKey("Tb2Id");
                            x.ToTable("Tb1Tb2");
                        }
                );

        }
    }

    public class Tb2Map : EntityTypeConfiguration<Tb2>
    {
        public Tb2Map()
        {
            this.HasKey(x => x.Id);
        }
    }
موقعی که در برنامه به صورت زیر استفاده می‌کنم:
            var sv1 = new TableService<Tb1>(_uow);
            var sv2 = new TableService<Tb2>(_uow);

            var t1 = new Tb1 { NameTb1 = "T111" };
            sv1.Add(t1);
            //var res1= _uow.SaveChanges();
            
            var t2 = new Tb2 { NameTb2 = "T222" };
            sv2.Add(t2);
            //var res2 = _uow.SaveChanges();

            t1.ListTb2.Add(t2);
            var result = _uow.SaveChanges();
 هنگام SaveChanges این خطا رو می‌ده: 
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.
همراه با innerException زیر:
{"The INSERT statement conflicted with the FOREIGN KEY constraint \"FK_Tb1Tb2_Tb2\". The conflict occurred in database \"dbTest\", table \"dbo.Tb2\", column 'Id'.\r\nThe statement has been terminated."}
در واقع همینطور که مشخصه من می‌خوام اون جدول رابطه رو در codeFirst حذف کنم یجورایی و رابطه رو بین 2 جدول اصلی بیارم. کجای کارم اشتباهه؟ و راهکارش چیه؟
من با پروفایلر هم نگاه کردم همه چی تا آخر داره پیش می‌ره!
(آیا ForeignKey رو باید طور دیگه ای تعریف کنم؟) 
با تشکر
مطالب
بررسی مفهوم Captured Variable در زبان سی شارپ
Capturing Outer Variables  
یک عبارت لامبدا می‌تواند از متغیرهای محلی و یا پارامترهای متدی که در آن تعریف شده است، استفاده نماید (Outer Variables). این متغیرها را captured variables می‌نامند. عبارت لامبدایی که از این متغیرها استفاده می‌کند، closure نامیده می‌شود. برای مثال:
static void Main()
{
 int factor = 2;
 Func<int, int> multiplier = n => n * factor;
 Console.WriteLine (multiplier (3)); // 6
}
در کد فوق multiplier یک delegate می‌باشد که ورودی صحیح n را گرفته و در مقدار factor ضرب کرده و بر می‌گرداند.

عبارت لامبدا زمانی ارزیابی می‌شود که delegate متناظر فراخوانی (Invoke) گردد؛ نه زمانیکه متغیر اصطلاحا capture می‌شود:
int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
در کد فوق در زمانی که multiplier فراخوانی می‌شود مقدار factor برابر 10 ارزیابی شده و لذا عدد 30 چاپ خواهد شد.

عبارات لامبدا خود می‌توانند captured variable‌ها را تغییر دهند:
int seed = 0;
Func<int> natural = () => seed++;
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 1
Console.WriteLine (seed); // 2
در کد فوق natural یک delegate بدون ورودی و با یک خروجی integer می‌باشد. در ابتدا متغیر محلی seed تعریف شده و با مقدار اولیه 0 مقداردهی می‌شود. با هر بار اجرای natural مقدار seed به اندازه 1 واحد افزایش می‌یابد.
طول عمر(lifetime) متغیرهای captured شده در حد طول عمر delegate افزایش پیدا می‌کند. در مثال زیر متغیر محلی seed در حالت معمول، محدوده دیدی (scope) در حد تعریف این متغیر تا پایان اجرای متد دارد. اما از آنجاییکه در اینجا متغیر captured شده است، طول عمر آن در حدا طول عمر delegate افزایش می‌یابد: theNatural
static Func<int> Natural()
{
 int seed = 0;
 return () => seed++; // Returns a closure
}
static void Main()
{
 Func<int> theNatural = Natural();
 Console.WriteLine (theNatural ()); // 0
 Console.WriteLine (theNatural ()); // 1
}
اگر متغیر seed را در بدنه عبارت لامبدا تعریف نماییم، این متغیر برای هر بار اجرای delegate یکتا خواهد بود:
static Func<int> Natural()
{
 return() => { int seed = 0; return seed++; };
}
static void Main()
{
 Func<int> natural = Natural();
 Console.WriteLine (natural()); // 0
 Console.WriteLine (natural()); // 0
}

نکته: پیاده سازی پروسه Capture شدن متغیر، به این صورت است که این متغیرها به عنوان یک فیلد از یک کلاس (با سطح دسترسی private) در نظر گرفته می‌شوند. زمانیکه متد فراخوانی شد، کلاس مزبور وهله سازی شده و طول عمر آن به  طول عمر delegate گره می‌خورد.

Capturing iteration variables
در حلقه for، وقتی که متغیر حلقه توسط یک عبارت لامبدا capture می‌گردد، #C با آن متغیر طوری رفتار می‌کند که گویی در خارج از حلقه تعریف شده‌است و این بدان معناست که در هر بار تکرار حلقه، مقدار یکسانی برای متغیر در نظر گرفته می‌شود. کد زیر 333 را در خروجی چاپ می‌کند(بجای 012). 
Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
actions [i] = () => Console.Write (i);
foreach (Action a in actions) a(); // 333
دلیل این موضوع این است که در هنگام اجرای delegate ها، هر delegate مقدار i را برابر مقدار آن در زمان اجرا می‌بیند و این مقدار در زمان اجرا برابر با 3 می‌باشد.
با نوشتن کد زیر می‌توان درک بهتری از موضوع پیدا کرد. 
Action[] actions = new Action[3];
int i = 0;
actions[0] = () => Console.Write (i);
i = 1;
actions[1] = () => Console.Write (i);
i = 2;
actions[2] = () => Console.Write (i);
i = 3;
foreach (Action a in actions) a(); // 333
اگر بخواهیم خروجی 012 چاپ شود راه حل به شرح زیر خواهد بود:
Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
{
 int loopScopedi = i;
 actions [i] = () => Console.Write (loopScopedi);
}
foreach (Action a in actions) a(); // 012
زیرا هر متغیر loopScopedi در هر بار تکرار حلقه مجددا تعریف می‌گردد و لذا هر بار متغیر متفاوتی capture می‌گردد.
اشتراک‌ها
نحوه‌ی آفلاین کردن یک سایت ASP.NET 5 برای تعمیرات

ASP.NET 2.0 introduced a concept of application offline. This mean that when there is App_Offline.htm file in the root of a web application directory then ASP.NET will shut-down the application, unload the application domain from the server, and stop processing any new incoming requests for that application. In ASP.NET 5, there is an open-issue for supporting this feature. 

نحوه‌ی آفلاین کردن یک سایت ASP.NET 5 برای تعمیرات
اشتراک‌ها
آیا باید همه اطلاعات در وب رمز شود ؟

  Should All Web Traffic Be Encrypted? The prevalence of free, open WiFi has made it rather easy for a WiFi eavesdropper to steal your identity cookie for the websites you visit while you're connected to that WiFi access point   

آیا باید همه اطلاعات در وب رمز شود ؟