به زبان ساده، وقتی شما متغیر جدیدی را ایجاد میکنید، با توجه به نوع (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. وارد عمل میشود.
نکاتی درباره برنامه نویسی دستوری(امری)
mutable Keyword
در فصل دوم(شناسه ها) گفته شد که برای یک شناسه امکان تغییر مقدار وجود ندارد. اما در #F راهی وجود دارد که در صورت نیاز بتوانیم مقدار یک شناسه را تغییر دهیم.در #F هرگاه بخواهیم شناسه ای تعریف کنیم که بتوان در هر زمان مقدار شناسه رو به دلخواه تغییر داد از کلمه کلیدی mutable کمک میگیریم و برای تغییر مقادیر شناسهها کافیست از علامت (->) استفاده کنیم. به یک مثال در این زمینه دقت کنید:
#1 let mutable phrase = "Can it change? " #2 printfn "%s" phrase #3 phrase <- "yes, it can." #4 printfn "%s" phrase
در خط اول یک شناسه را به صورت mutable(تغییر پذیر) تعریف کردیم و در خط سوم با استفاده از (->) مقدار شناسه رو update کردیم. خروجی مثال بالا به صورت زیر است:
Can it change? yes, it can.
نکته اول: در این روش هنگام update کردن مقدار شناسه حتما باید مقدار جدید از نوع مقدار قبلی باشد در غیر این صورت با خطای کامپایلری متوقف خواهید شد.
#1 let mutable phrase = "Can it change? " #3 phrase <- 1
اجرای کد بالا خطای زیر را به همراه خواهد داشت.(خطا کاملا واضح است و نیاز به توضیح دیده نمیشود)
Prog.fs(9,10): error: FS0001: This expression has type int but is here used with type string
let redefineX() = let x = "One" printfn "Redefining:\r\nx = %s" x if true then let x = "Two" printfn "x = %s" x printfn "x = %s" x
در مثال بالا در تابع redefineX یک شناسه به نام x تعریف کردم با مقدار "One". یک بار مقدار شناسه x رو چاپ میکنیم و بعد دوباره بعد از شرط true یک شناسه دیگر با همون نام یعنی x تعریف شده است و در انتها هم دو دستور چاپ. ابتدا خروجی مثال بالا رو با هم مشاهده میکنیم.
Redefining: x = One x = Two x = One
let mutableX() = let mutable x = "One" printfn "Mutating:\r\nx = %s" x if true then x <- "Two" printfn "x = %s" x printfn "x = %s" x
Mutating: x = One x = Two x = Two
Reference Cells
روشی برای استفاده از شناسهها به صورت mutable است. با این روش میتونید شناسه هایی تعریف کنید که امکان تغییر مقدار برای اونها وجود دارد. زمانی که از این روش برای مقدار دهی به شناسهها استفاده کنیم یک کپی از مقدار مورد نظر به شناسه اختصاص داده میشود نه آدرس مقدار در حافظه.
به جدول زیر توجه کنید:
Member Or Field | Description | Definition |
(derefence operator)! | مقدار مشخص شده را برگشت میدهد | let (!) r = r.contents |
(Assignment operator)=: | مقدار مشخص شده را تغییر میدهد | let (:=) r x = r.contents <- x |
ref operator | یک مقدار را در یک reference cell جدید کپسوله میکند | let ref x = { contents = x } |
Value Property | برای عملیات get یا set مقدار مشخص شده | member x.Value = x.contents |
contents record field | برای عملیات get یا set مقدار مشخص شده | let ref x = { contents = x } |
let refVar = ref 6 refVar := 50
printfn "%d" !refVar
let xRef : int ref = ref 10 printfn "%d" (xRef.Value) printfn "%d" (xRef.contents) xRef.Value <- 11 printfn "%d" (xRef.Value) xRef.contents <- 12 printfn "%d" (xRef.contents)
10 10 11 12
خصیصه اختیاری در #F
در #F زمانی از خصیصه اختیاری استفاده میکنیم که برای یک متغیر مقدار وجود نداشته باشد. option در #F نوعی است که میتواند هم مقدار داشته باشد و هم نداشته باشد.
let keepIfPositive (a : int) = if a > 0 then Some(a) else None
let exists (x : int option) = match x with | Some(x) -> true | None -> false
چگونگی استفاده از option
مثال
let rec tryFindMatch pred list = match list with | head :: tail -> if pred(head) then Some(head) else tryFindMatch pred tail | [] -> None let result1 = tryFindMatch (fun elem -> elem = 100) [ 200; 100; 50; 25 ] //برابر با 100 است let result2 = tryFindMatch (fun elem -> elem = 26) [ 200; 100; 50; 25 ]// برابر با None است
یک مثال کاربردی تر
open System.IO let openFile filename = try let file = File.Open (filename, FileMode.Create) Some(file) with | ex -> eprintf "An exception occurred with message %s" ex.Message None
Enumeration
تقریبا همه با نوع داده شمارشی یا enums آشنایی دارند. در اینجا فقط به نحوه پیاده سازی آن در #F میپردازیم. ساختار کلی تعریف آن به صورت زیر است:
type enum-name = | value1 = integer-literal1 | value2 = integer-literal2 ...
type Color = | Red = 0 | Green = 1 | Blue = 2
let col1 : Color = Color.Red
type uColor = | Red = 0u | Green = 1u | Blue = 2u let col3 = Microsoft.FSharp.Core.LanguagePrimitives.EnumOfValue<uint32, uColor>(2u)
توضیح درباره use
در دات نت خیلی از اشیا هستند که اینترفیس IDisposable رو پیاده سازی کرده اند. این بدین معنی است که حتما یک متد به نام dispose برای این اشیا وجود دارد که فراخوانی آن به طور قطع باعث بازگرداندن حافظه ای که در اختیار این کلاسها بود میشود. برای راحتی کار در #C یک عبارت به نام using وجود دارد که در انتها بلاک متد dispose شی مربوطه را فراخوانی میکند.
using(var writer = new StreamWriter(filePath)) { }
let writeToFile fileName = use sw = new System.IO.StreamWriter(fileName : string) sw.Write("Hello ")
در #F اعداد دارای علامت و اعداد شناور دارای وابستگی با واحدهای اندازه گیری هستند که به نوعی معرف اندازه و حجم و مقدار و ... هستند. در #F شما مجاز به تعریف واحدهای اندازه گیری خاص خود هستید و در این تعاریف نوع عملیات اندازه گیری را مشخص میکنید. مزیت اصلی استفاده از این روش جلوگیری از رخ دادن خطاهای کامپایلر در پروژه است. ساختار کلی تعریف:
[<Measure>] type unit-name [ = measure ]
[<Measure>] type cm
[<Measure>] type ml = cm^3
let value = 1.0<cm>
قدرت اصلی واحدهای اندازه گیری #F در توابع تبدیل است. تعریف توابع تبدیل به صورت زیر میباشد:
[<Measure>] type g تعریف واحد گرم [<Measure>] type kg تعریف واحد کیلوگرم let gramsPerKilogram : float<g kg^-1> = 1000.0<g/kg> تعریف تابع تبدیل
[<Measure>] type degC // دما بر حسب سلسیوس [<Measure>] type degF // دما بر حسب فارنهایت let convertCtoF ( temp : float<degC> ) = 9.0<degF> / 5.0<degC> * temp + 32.0<degF> // تابع تبدیل سلسیوس به فارنهایت let convertFtoC ( temp: float<degF> ) = 5.0<degC> / 9.0<degF> * ( temp - 32.0<degF>) // تابع تبدیل فارنهایت به سلسیوس let degreesFahrenheit temp = temp * 1.0<degF> // درجه به فارنهایت let degreesCelsius temp = temp * 1.0<degC> // درجه به سلسیوس printfn "Enter a temperature in degrees Fahrenheit." let input = System.Console.ReadLine() let mutable floatValue = 0. if System.Double.TryParse(input, &floatValue)// اگر ورودی عدد بود then printfn "That temperature in Celsius is %8.2f degrees C." ((convertFtoC (degreesFahrenheit floatValue))/(1.0<degC>)) else printfn "Error parsing input."
خروجی مثال بالا :
Enter a temperature in degrees Fahrenheit. 90 That temperature in degrees Celsius is 32.22.
معرفی WebAssembly بر روی Server
WebAssembly is moving beyond the browser and is pitched to become a foundational element of modern cloud-native architecture. It lets any language compile to universal binaries that run on any OS or processor, robustly sandboxed and with great performance. This session covers a new approach to running .NET in WASI environments. You’ll see how your existing .NET code could be built into WASI-compliant modules, plus the opportunities this opens. This is experimental, not yet a committed product.
استفاده از LINQ در JavaScript
NetSqlAzMan
- NetSqlAzMan allows you to change User Authorizations without recompile your application !
- NetSqlAzMan supports AOP (Aspect Oriented Programming
کتابخانه k6
public partial class product : PageStateMachine { protected void btnSave_Click(object sender, EventArgs e) { Go(PageState.Save); } protected override ... Save() { try { if (Id > 0) { // get from bank and update } else { // create new one and add it to context } } catch (Exception ex) { // handle exception } } protected void btnSelect_Click(object sender, EventArgs e) { Go(PageState.Select); } protected override ... Select() { try { // get from bank and show in details } catch (Exception ex) { // handle exception } } protected void btnDelete_Click(object sender, EventArgs e) { Go(PageState.Delete); } protected override StateMachineMessage Delete() { try { // remove from bank } catch (Exception ex) { // handle exception } } }
جادوی زبان، طلسم پیچیدگی
زبان کارکردی دوگانه دارد. هم میتواند چونان جادوگری بد، بر ابهامات بیفزاید و بر حقایق پرده بکشد و هم میتواند مانند جادوگری خوب، کاشف نکات و گرهگشای مسائل دامین باشد. به همین دلیل است که در DDD بر ایجاد زبانی مشترک و پیراسته از ابهام تاکید بسیاری شده است. در این ارایه،از اهمیت Ubiquitous Language سخن گفته میشود و به مشکلات خاص فارسیزبانان در ایجاد UL میپردازد.