به زبان ساده، وقتی شما متغیر جدیدی را ایجاد میکنید، با توجه به نوع (Type) آن متغیر، "مقدار" متغیر شما در Stack یا Heap قرار خواهد گرفت.
Stack
این ساختمان داده، داخل Memory پیاده سازی شده است و تعدادی از متغیرهایی را که ما داخل کد ایجاد میکنیم، در این نوع ساختمان داده از Memory نگهداری میشوند.
شرط قرار گرفتن مقدار یک متغیر داخل Stack این است که متغیر از نوع Value Type باشد. در زبان #C، بطور کلی Struct و Enumها Value Type هستند و بصورت پیشفرض داخل Stack قرار میگیرند. تمامی ValueTypeها در #C، بطور implicit از System.ValueType ارث بری میکنند.
Typeهای زیر، Value Typeهای پیشفرض تعریف شدهی در زبان #C هستند که به آنها Simple Type نیز گفته میشوند:
Represents | Type |
Boolean value | bool |
8-bit unsigned integer | byte |
16-bit Unicode character | char |
128-bit precise decimal values with 28-29 significant digits | decimal |
64-bit double-precision floating point type | double |
32-bit single-precision floating point type | float |
32-bit signed integer type | int |
64-bit signed integer type | long |
8-bit signed integer type | sbyte |
16-bit signed integer type | short |
32-bit unsigned integer type | uint |
64-bit unsigned integer type | ulong |
16-bit unsigned integer type | ushort |
اگر سورس هرکدام از این تایپها مانند Int32 را در ریپازیتوری CoreFX مایکروسافت بررسی کنید، متوجه خواهید شد که تمامی این تایپها از نوع Struct تعریف شدهاند و همانطور که گفتیم، بطور پیشفرض، Structها داخل Stack قرار خواهند گرفت.
طول عمر متغیرهایی که داخل Stack قرار گرفتهاند، منحصر به پایان اجرای یک متد است. بدین معنا که بعد از به پایان رسیدن یک متد، تمامی متغیرهای مورد استفاده در آن متد، از حافظه Stack بطور خودکار حذف خواهند شد. متغیرهایی که داخل Stack قرار میگیرند، نوع و حجم مقادیرشان بر اساس Type ای دارند، در زمان Compile-Time مشخص است.
public static int Add(int number1, int number2) { // number1 is on the stack (function parameter) // number2 is on the stack (function parameter) int sum = number1 + number2; // sum is on the stack (local variable) return sum; }
در زبان #C و در مرحله Compile-Time، کدها به زبان IL (مخفف Intermediate Language) ترجمه میشوند که با نامهای MSIL (مخفف Microsoft Intermediate Language ) و CIL (مخفف Common Intermediate Language ) نیز، این زبان شناخته میشود. ساختار این زبان Stack-based بوده و با شناخت آن، با مفهوم Stack نیز بهتر میتوانیم آشنا شویم.
IL زبانی است که CLR (مخفف Common Language Runtime) را که همان Runtime مایکروسافت است، شناخته و اجرا میکند. قابل ذکر است که Runtime مایکروسافت Open-Source بوده و سورس آن با نام CoreCLR در گذشته از این آدرس و در حال حاضر با نام Runtime از این آدرس قابل دسترسی است.
C:\Program Files (x86)\Microsoft SDKs\Windows\{version}\Bin\ildasm.exe
.method private hidebysig static int32 Add(int32 number1, int32 number2) cil managed { .locals init (int32 V_0, int32 V_1) IL_0001: ldarg.0 // Stack is: [2] IL_0002: ldarg.1 // Stack is: [2, 5] IL_0003: add // Stack is: [7] IL_0004: stloc.0 // Stack is: [] and V_0's value is: 7 IL_0005: ldloc.0 // Stack is: [7] IL_0006: stloc.1 // Stack is: [] and V_1's value is: 7 IL_0009: ldloc.1 // Stack is: [7] IL_000a: ret // Return [7] }
میتوانید لیست دستورات مورد استفاده در CIL را از اینجا ببینید.
در ادامه، خط به خط، خروجی حاصل را بررسی میکنیم:
2- از کلمه کلیدی ldarg (مخفف Load Argument) برای لود کردن آرگومان یا همان پارامتر ورودی متد، داخل Stack استفاده میشود.
• ldarg.0 به معنای لود کردن پارامتر ورودی اول، داخل Stack است و با فراخوانی آن، Stack Frame دارای یک عضو که مقدار آن 2 است، میشود.
3- با استفاده از کلمه کلیدی add، مقادیر موجود در Stack با یکدیگر جمع میشوند و Stack Frame دارای یک عضو که مقدار آن 7 است، میشود.
4- با استفاده از کلمه کلیدی stloc (مخفف Store Local)، آخرین عضو موجود در Stack، داخل متغیر محلی ذکر شده، قرار گرفته و ذخیره میشود.
5- با استفاده از کلمه کلیدی ldloc (مخفف Load Local)، میتوان متغیر محلی ذخیره شده را داخل Stack قرار داد.
6- در نهایت، مقدار 7، داخل متغیر 1 یا همان V_1 با دستور stloc.1 بار دیگر ذخیره، با ldloc.1 لود شده و با استفاده از دستور ret، برگشت داده میشود.
* نکته: اگر کدها را بطور دقیق بررسی کرده باشید، احتمالا فکر کرده اید که چه نیازی به ایجاد یک متغیر اضافی و ریختن نتیجه داخل آن و سپس برگشت دادن نتیجه، در مرحله 6 است؟!
* نکته: احتمالا تا به اینجا دلیل بوجود آمدن StackOverflowException را متوجه شده باشید. فضای Stack محدود است. این فضا در سیستمهای 32 بیت برابر با 1 مگابایت و در سیستمهای 64 بیت برابر با 4 مگابایت است (Reference). اگر حجم متغیرهایی که روی استک Push میشوند، این محدودیت را رد کنند و یا اگر یک متد بطور دائم خودش را صدا بزند (Recursive) و هیچگاه از آن خارج نشود، با خطای StackOverflowException مواجه میشوید.
Heap
.Heap: a group of things placed, thrown, or lying one on another
در مقابل ساختار ترتیبی و منظم Stack، ساختار Heap قرار دارد. Heap قسمتی از حافظه است که ساختار، ترتیب و Layout خاصی ندارد.
وقتی یک متغیر از نوع string را ایجاد میکنیم، مقدار آن داخل Heap و Memory-Address آن متغیر روی Heap، در Stack نگه داری میشود:
public static void SayHi() { string name = "Moien"; }
در این مثال، چون string یک class است، مقدار آن داخل heap ذخیره شده و آدرس آن قسمت (segment) از memory، روی Stack قرار میگیرد:
.method private hidebysig static void SayHi() cil managed { .locals init (string V_0) IL_0001: ldstr "Moien" // Stack is: [memory-address of string in heap] IL_0006: stloc.0 IL_0007: ret }
به متغیرهایی که مقادیرشان داخل Heap ذخیره میشوند، Reference-Type گفته میشود.
* نکته: در این مثال متغیری به نام name ایجاد شده که از آن هیچ استفادهای نشده است. در زمان JIT-Compilation، با توجه با Optimizationهای موجود در سطح CLR، این متد بطور کلی اضافه تشخیص داده شده و از آن صرفنظر خواهد شد.
Boxing and Unboxing
به فرایند تبدیل یک Value-Type مانند int که بصورت پیشفرض داخل Stack ذخیره میشود، به یک object که در داخل Heap ذخیره میشود، Boxing گفته میشود. انجام این عمل باعث allocation بر روی memory میشود که سربار زیادی دارد.
با انجام عمل Boxing، قادر خواهیم بود تا بعنوان مثال یک عدد را بر خلاف روال عادی آن، روی Heap ذخیره کنیم:
public static void Boxing() { const int number = 5; object boxedNumber = number; // implicit boxing using implicit cast object boxedNumber = (object)number; // explicit boxing using direct cast }
در ابتدا عدد 5 روی Stack ذخیره شده بود، اما با Box کردن آن، یعنی قرار دادن مقدار آن داخل یک object، مقدار از Stack به Heap انتقال داده شده و allocation اتفاق خواهد افتاد:
.method public hidebysig static void Boxing() cil managed { .locals init (object V_0) IL_0001: ldc.i4.5 // Stack is: [5] IL_0002: box [System.Runtime]System.Int32 // Stack is: [memory-address of 5 in heap] IL_0007: stloc.0 IL_0008: ret }
به عکس این عمل، یعنی تبدیل یک Reference-Type به یک Value-Type، اصطلاحا Unboxing گفته میشود:
public static void Unboxing() { object boxedNumber = 5; int number = (int)boxedNumber; }
که نتیجه آن، به این صورت خواهد بود:
.method public hidebysig static void Unboxing() cil managed { .locals init (object V_0, int32 V_1) IL_0001: ldc.i4.5 // Stack is: [5] IL_0002: box [System.Runtime]System.Int32 // Stack is: [memory-address of 5 in heap] IL_0007: stloc.0 // Stack is: [] IL_0008: ldloc.0 // Stack is: [memory-address of 5 in heap] IL_0009: unbox.any [System.Runtime]System.Int32 // Stack is: [5] IL_000e: stloc.1 // Stack is: [] IL_000f: ret }
تلاش تیمهای مایکروسافت طی سالهای اخیر، باعث افزایش Performance فوق العاده در NET Core. و ASP.NET Core شده است. یکی از دلایل این Performance، جلوگیری بسیار زیاد از allocation در کدهای خود NET. است، که این امر به واسطه اولویت قرار دادن استفاده از Structها میسر گردیده است.
برخلاف Stack که طول عمر متغیرهای موجود در آن، در انتهای یک متد پایان مییابند، متغیرهای allocate شدهی در Heap به این شکل نبوده و در صورت حذف نکردن آنها بصورت دستی، تا پایان طول عمر اجرای برنامه داخل memory باقی خواهند ماند. اینجا، جاییست که Garbage Collector در NET. وارد عمل میشود.
کنترلهای زیر جهت ورود اطلاعات در ویرایشگر پشتیبانی میشوند:
- text
- textarea
- select
- date
- datetime
- dateui
- combodate
- html5types
- checklist
- wysihtml5
- typeahead
- typeaheadjs
- select2
clear | دکمهای جهت حذف محتوای کادر متنی است. مقدار پیش فرض آن true است. |
escape | برای
دفاع در برابر کدهای مخرب html به کار میرود و کاراکترهای مدنظر را در صورت
true بودن غیرفعال میکند. البته اگر از خاصیت display استفاده کنید این
گزینه تاثیرش را از دست خواهد داد. |
inputclass | یک کلاس css را به کادر متنی اعمال میکند. |
placeholder | مقدار داده شده را در صورتی که کادر متنی خالی باشد، نشان میدهد. |
tpl | به معنی یک قالب. شما میتوانید کد html تگ input خود را وارد کنید؛ ولی توصیه نمیشود. |
TextArea
همان خاصیتهای قبلی را دارد بعلاوه rows که نمایانگر مقدار ارتفاع آن است.
select
خاصیتهای escape,input,class و tpl را دارد بهعلاوه خاصیتهای زیر:
prepend | همانند گزینه پایینی است ولی قبل از آن دادههای خود را اضافه میکند. |
source | از
آنجا که یک لیست، لیستی از آیتمها را دارد و کاربر یکی از آنها را
انتخاب میکند، این بخش، منبع آیتمها را معرفی میکند. این خاصیت چهار نوع
داده میپذیرد: آرایه یا شیءایی از مقادیر. تابعی که بعد از انجام هر عملی،
اطلاعات به آن پاس میشوند و یا از نوع رشته که این رشته یک آدرس سمت سرور
است که با درخواست از آن آدرس، اطلاعات را دریافت میکند. |
sourceCache | اگه خاصیت بالا با آدرسی پر شده باشد که از سمت سرور بخواند، در دفعات بعدی مقدار دریافتی را از کش خواهد خواند. |
sourceError | یک پیام خطا هنگام بارگزاری اطلاعات |
sourceOptions | در
صورتیکه قصد اضافه کردن پارامتری را به درخواست ایجکسی دارید. یک شیء از پارامترها را به آن
نسبت میدهیم و برای رونویسی پارامترها از یک تابع استفاده میکنیم که نحوهی تغییرات را قبلا در جدول شماره یک دیدهاید. |
date
خاصیتهای مشترک قبلی : tpl,input,class,escape و clear است.
datepicker | پیکربندی تقویم را بر عهده دارد. برای اطلاعات بیشتر در مورد پیکربندی تقویم به این لینک مراجعه فرمایید.{ weekStart: 0, startView: 0, minViewMode: 0, autoclose: false } |
format | قالب بندی فرمت تاریخ جهت ارسال به سرور\ حالت پیش فرض yyyy-mm-dd مقادیری که میتوان به کار برد: yy yyyy mm m dd d |
viewformat | این فرمت هنگام نمایش به کار میآید و در صورتیکه مقدار عنصر در این قالب نباشد، آن را تبدیل میکند. |
datetime در بوت استراپ
کاملا مشترک با مورد قبلی.
dateUI
مختص JqueryUI است و کاملا مشترک با مورد قبلی.
combodate
موارد مشترک قبلی را دارد ولی به جای خاصیت datepicker از combodate استفاده میشود که پیکربندی آن در این لینک قرار دارد.
نوعهای HTML 5
شامل موارد زیر است:
- password
- url
- tel
- number
- range
- time
خاصیتهای ذکر شده در مورد نوع text، در مورد آنها نیز صدق میکند.
checklist
همانند نوع select است؛ فقط خاصیت separator را دارد که کارش جدا کردن مقادیر است و مقدار پیش فرض آن علامت ',' است.
wysihtml5
سورس و دمو ی این نوع ادیتور که بر پایهی بوت استرپ بنا شده است و زحمت اضافه کردن کتابخانهها به صفحه، بر عهده شماست.
مداخل زیر را به طور دستی به صفحه اضافه کنید:
<link href="js/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.css" rel="stylesheet" type="text/css"></link> <script src="js/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/wysihtml5-0.3.0.min.js"></script> <script src="js/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.min.js"></script>
<script src="js/inputs-ext/wysihtml5/wysihtml5.js"></script>
typeahead
این گزینه فقط مختص بوت استرپ 2 است و یک کنترل autocomplete به شمار میآید. منبع دادههای آن از طریق خاصیت source به دو صورت آرایه و object تامین میگردد.
['text1', 'text2', 'text3' ...] //or [{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]
typeaheadjs
همانند قبلی است و بر اساس twitterBootstrap است و شامل همان خصوصیات قبلی است. تنها خصوصیت typeahead آن است که باید از این پیکربندی استفاده کنید.
Select2
این المان بر اساس این کتابخانه سورس باز ایجاد میشود. و مستندات آن شامل جزئیات و پیکربندی آن میشود. برای معرفی آن فایلهای زیر را به صفحه معرفی کنید.
<link href="select2/select2.css" rel="stylesheet" type="text/css"></link> <script src="select2/select2.js"></script>
<link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link>
نکته: در حال حاضر خاصیت autotext روی این المان جواب نمیدهد و میتوانید از خاصیت data-value به جای آن استفاده کنید.
قالبی نو برای ویرایشگر
ویرایشگر فعلی ما به خصوص در بوت استرپ، ظاهر فوق العادهای دارد. ولی اگر بازهم مایل به تغییر و رونویسی هستید، این امکان فراهم شده است.
fn.editableform.template$
مقدار پیش فرض آن که حتما باید شامل تگ فرم و کلاسهای مدنظر باشد:
<form> <div> <div><div></div><div></div></div> <div></div> </div> </form>
- control-group
- editable-input
- editable-buttons
- editable-error-block
fn.editableform.buttons$
<button type="submit">ok</button> <button type="button">cancel</button>
و نهایتا جهت تغییر loading
fn.editableform.loading$
<div></div>
گاهی اوقات نیاز است که خصوصیات این ویرایشگر را در شرایط متغیر صفحه کنترل کنیم، برای مثال گاهی پیش میآید که بخواهید در یک شرایط خاص ویرایشگر یک المان خاص را غیرفعال کنید. کد زیر مثال این تغییرات است.
$('#favsite').editable('option', 'disabled', false);
متدها و رویدادها
متدهایی که روی آن قابل اجراست:
editable | ویرایشگر را بر اساس مقادیر اولیه روی عنصر مشخص شده فعال میکند. |
() activate | فوکوس را به input ویرایشگر باز میگرداند. |
() destory | حذف ویژگی ویرایش از روی عنصر |
() disable | غیرفعال کردن ویرایشگر |
() enable | فعال سازی آن |
()getvalue | باعث بازگردانی
مقدار جاری همه عناصر توسط شیء جفت کلید مقدار میشود و عناصری که شامل
متن یا مقداری نیستند، از آن حذف میشوند. در صورتیکه قصد دارید مقدار تنها
یک عنصر قابل دریافت باشد، با خاصیت isSingle آن را true کنید. $('#username, #fullname').editable('getValue'); //result: { username: "superuser", fullname: "John" } //isSingle = true $('#username').editable('getValue', true); //result "superuser" |
()hide | مخفی کردن تگ فرم ویرایشگر |
(option(key,value | تغییر خصوصیات یک عنصر که در بالا هم نمونه کد آن را دیدیم. |
(setvalue(value,convertStr | ست کردن مقدار جدید کنترل و پارامتر دوم وضعیت تبدیل این مقدار به فرمت داخلی است که برای آن تعریف شده است مثل date |
() show | نمایش ویرایشگر |
( submit(options | در
صورتی که خاصیت ارسال خودکار به سمت سرور را غیر فعال کرده باشید، با این
گزینه میتوانید همه اطلاعات و تغییرات را ارسال کنید. برای ایجاد فرم بر
اساس ویرایشگرها و ارسال اطلاعات با کلیک بر روی دکمه submit کاربرد دارد. یک مثال
در این زمینه . پارامترهای options به شرح زیر هستند: url data ajaxoptions (error(obj (success(obj,config از نسخه 1.5.1 میتوان این گزینه را به راحتی روی یک المان خاص هم صدا زد: $('#username').editable('submit') |
() toggle | کدی که صدا زده میشود بین دو وضعیت show و hide سوئیچ میکند. |
() toggleDisabled | تغییر وضعیت بین دو حالت enable و disable |
() validate | انجام اعتبارسنجی بر روی همه کنترل ها.$('#username, #fullname').editable('validate'); // possible result: { username: "username is required", fullname: "fullname should be minimum 3 letters length" } |
رویدادها
hidden | این
رویداد زمانی رخ میدهد که ویرایشگر دیگر قابل مشاهده نیست و شامل دو پارامتر
event و reason است. reason دلیل اینکه چرا ویرایشگر از دید خارج شده است
را با یکی از گزینههای زیر مشخص میکند. save cancel onblur nochange manual $('#username').on('hidden', function(e, reason) { if(reason === 'save' || reason === 'cancel') { //auto-open next editable $(this).closest('tr').next().find('.editable').editable('show'); } }); |
init | موقعی صدا زده میشود که متد editable روی عنصر صدا زده میشود و به یاد داشته باشید که این رویداد باید قبل از آن ست شده باشد.$('#username').on('init', function(e, editable) { alert('initialized ' + editable.options.name); }); $('#username').editable(); |
save | موقعی
که مقدار جدید، با موفقیت تایید میشود. دو پارامتر event و params را باز
میگرداند که params شامل دو خصوصیت newValue و response است که به ترتیب
مقدار جدید و اطلاعات برگشت داده شده از درخواست آژاکس است.$('#username').on('save', function(e, params) { alert('Saved value: ' + params.newValue); }); |
shown | موقعیکه ویرایشگر نمایش مییابد و فرم با موفقیت رندر شده است. برای اشیایی چون select باید صبر کنید تا مقادیر آنها بارگذاری شوند.$('#username').on('shown', function(e, editable) { editable.input.$input.val('overwriting value of input..'); }); |
حل مشکل این ابزار در کندو
موقعیکه من این ابزار را بر روی treeview قرار دادم، به این مشکل برخوردم که اطراف پنجره باز شده، توسط حاشیههای treeview محدود شده است و مطابق شکل زیر قسمتهایی از آن دیده نمیشود. به همین علت cssهای کندو را به اندازه یک خط ویرایش کردم.
برای حل این مشکل فایل kendo.common-xxx را باز کنید. xxx بر اساس قالبی که برای کندو انتخاب کردهاید، میتواند متفاوت باشد. در مثالهای کندو عموما این xxx به نام default شناخته میشود یا برای مثال من، bootstrap بود.
بعد از اینکه باز کردید، به دنبال چنین استایلی بگردید:
div.k-treeview{ border-width: 0px; background: transparent none repeat scroll 0px center; overflow: auto; white-space: nowrap; }
overflow: auto;
نکته بعدی اینکه وقتی ویرایشگر در حالت popup قرار میگیرد، مقدار خاصیت title نمایش مییابد که عموما با مضامینی چون "کلمه جدید را وارد نمایید" و ... پر میشود که به طور پیش فرض سمت چپ قرار گرفته است. کد زیر را در صفحه وارد کنید تا متن در سمت راست قرار بگیرد:
.popover-title { text-align: right; }
روش تعریف خواص سفارشی MSBuild در پروژهی Source Generator
در مثال همین سری، به پوشهی NotifyPropertyChangedGenerator مراجعه کرده و فایل جدید NotifyPropertyChangedGenerator.props را با محتوای زیر به آن اضافه میکنیم:
<Project> <ItemGroup> <CompilerVisibleProperty Include="SourceGenerator_CustomRootNamespace"/> </ItemGroup> </Project>
<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>
یک نکتهی مهم! اگر بخواهیم مستقیما از پروژهی 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>
روش دسترسی به مقدار خاصیت سفارشی 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; } }
[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>
خاصیت 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>
ب) در فایل 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>
ج) برای خواندن این خواص در پروژهی 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; } }
- روش تامین 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... }
مطابق RFC مربوطه، اگر هدر درخواست ارسالی به سرور را کمی تغییر دهیم میتوان بجای شروع از اولین بایت، از بایت مورد نظر شروع به دریافت فایل نمود. (البته این به شرطی است که سرور آنرا پشتیبانی کند)
یعنی نیاز داریم که به هدر ارسالی سطر زیر را اضافه کنیم:
Range: bytes=n-
برای بدست آوردن اندازهی فایل ناقص موجود میتوان از دستور زیر استفاده کرد:
using System.IO;
long brokenLen = new FileInfo(fileNamePath).Length;
سپس اگر شیء webRequest ما به صورت زیر تعریف شده باشد:
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
فقط کافی است سطر زیر را جهت افزودن قابلیت از سرگیری مجدد دریافت فایل به این شیء افزود:
//دانلود از ادامه
webRequest.AddRange((int)brokenLen); //resume
نکته:
اگر علاقمند باشید که ریز فعالیتهای انجام شده توسط فضای نام System.Net را ملاحظه کنید، به فایل config خود (مثلا فایل app.config برنامه)، چند سطر زیر را اضافه کنید:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.diagnostics>
<trace autoflush="true" />
<sources>
<source name="System.Net">
<listeners>
<add name="MyTraceFile"/>
</listeners>
</source>
</sources>
<sharedListeners>
<add
name="MyTraceFile"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="System.Net.trace.log"
/>
</sharedListeners>
<switches>
<add name="System.Net" value="Verbose" />
</switches>
</system.diagnostics>
</configuration>
ملاحظات:
بدیهی است پیاده سازی قابلیت resume نیاز به موارد زیر خواهد داشت:
الف) در نظر گرفتن مسیری پیش فرض برای ذخیره سازی فایلها
ب) پیدا کردن اندازهی فایل موجود بر روی یک سرور و مقایسهی آن با حجم فایل موجود بر روی هارد
امکان پیدا کردن اندازهی یک فایل هم بدون دریافت کامل آن میسر است. خاصیت ContentLength مربوط به شیء HttpWebResponse بیانگر اندازهی یک فایل بر روی سرور است و صد البته پیش از استفاده از این عدد، مقدار StatusCode شیء نامبرده را بررسی کنید. اگر مساوی OK بود، یعنی این عدد معتبر است.
در ابتدای کار نیاز است تا repository خود را ایجاد کنیم. بدین منظور از طریق محیط command prompt به آدرس پوشه مورد نظر رفته و دستور git init را اجرا میکنیم. این کار سبب میشود تا پوشه git. در داخل فولدر جاری ایجاد شود. این پوشه در واقع همان repository و پوشه جاری، همان working tree ما خواهند بود. حال با استفاده از یک ادیتور نظیر notepad یک فایل متنی جدید را با نام readme1.txt در پوشه ایجاد کنید (توجه کنید در working tree، نه در پوشه git.؛ محتویات این پوشه جز در مورد برخی فایلها نباید توسط کاربر تغییر کند)
اکنون دستور زیر را اجرا کنید:
git status
برای آنکه این فایل را در repository ذخیره کنیم همانطور که قبلا گفته شد باید ابتدا آنرا به index اضافه کنیم این کار با استفاده از دستور زیر انجام میشود:
git add readme1.txt
git commit
git commit -m “commit descriptions”
git add .
git add -u
git log
git log --until [date] git log --since [date] git log -[number]
چگونگی حذف فایلها:
تا اینجا با نحوه چگونگی ایجاد فایلهای جدید و یا ویرایش فایلهای قدیمی آشنا شدید. برای حذف یک فایل میتوان به دو صورت عمل کرد:
1) ابتدا فایل را را مستقیما حذف نموده، سپس با استفاده از دستور زیر ابتدا فایل حذف شده را به stage آورده و سپس آن را commit میکنیم:
git rm [filename]
چگونگی تغییر نام و یا جابجایی یک فایل:
برای تغییر نام و جابجایی یک فایل نیز مانند حذف، دو روش وجود دارد:
۱) ابتدا فایل مورد نظر را تغییر نام داده و یا جابجا میکنیم. در این حالت اگر status بگیریم خواهیم دید که git به ما میگوید فایلی با نام قبلی حذف شده و فایلی با نام جدید اضافه شده است. یعنی git تشخیص نمیدهد که این دو فایل یکی هستند و تنها تغییر نام داده شده است. اما به محض آنکه فایل اول را با دستور rm حذف و فایل دوم را با دستور add اضافه کنیم، git متوجه میشود که این دو فایل در واقع یک فایل تغییر نام یافته هستند. البته در صورتیکه حداقل ۵۰ درصد فایل دوم با فایل اول شباهت داشته باشد، بعد از انجام عملیات فوق از دستور commit استفاده میکنیم.
۲) در این روش از دستور زیر استفاده کرده و سپس commit را انجام میدهیم:
git mv [firstname][secondname]
روش اول :
When you go to https://vscode.dev, you'll be presented with a lightweight version of VS Code running fully in the browser. Open a folder on your local machine and start coding.
No install required.