پیشنهادها
نظرات مطالب
Angular CLI - قسمت سوم - تولید کد
«... شیوهنامهای که به این صورت توسط AngularJS 2.0 اضافه میشود (شیوهنامهی تعریف شدهی داخل یک کامپوننت)، با سایر شیوهنامههای موجود تداخل نخواهد کرد ...»
برای لغو این حالت میتوان از ViewEncapsulation.None استفاده کرد:
- حالت Emulated (حالت پیش فرض): شیوهنامههای HTML، به تمام کامپوننتها اعمال میشوند ولی نه برعکس.
- حالت Native: نه HTML و نه کامپوننتها، بر روی شیوهنامههای یکدیگر تاثیر نمیگذارند.
- حالت None: شیوهنامههای یک کامپوننت به کل برنامه منتشر شده و بر روی آن تاثیری میگذارند.
برای لغو این حالت میتوان از ViewEncapsulation.None استفاده کرد:
@Component({ // ... encapsulation: ViewEncapsulation.None,
- حالت Native: نه HTML و نه کامپوننتها، بر روی شیوهنامههای یکدیگر تاثیر نمیگذارند.
- حالت None: شیوهنامههای یک کامپوننت به کل برنامه منتشر شده و بر روی آن تاثیری میگذارند.
در C# 7.2 میتوان با value types (مانند structs) همانند reference types (مانند کلاسها) رفتار کرد. جائیکه کارآیی برنامه بسیار حائز اهمیت باشد (مانند بازیها)، استفاده از structs و value types بسیار مرسوم است؛ از این جهت که این نوعها بر روی heap تخصیص داده نمیشوند. اما مشکل آنها این است که زمانیکه به متدها ارسال میشوند، مقدار آنها ارسال خواهد شد و برای این منظور نیاز به ایجاد یک کپی جدید از آنها میباشد. برای رفع این مشکل و کاهش سربار کپی کردن اشیاء، اکنون در C# 7.2 میتوان value types را همانند reference types به متدها ارسال کرد.
واژهی کلیدی جدید in
C# 7.2، واژهی کلیدی جدیدی را به نام in جهت تعریف پارامترها، معرفی کردهاست. زمانیکه از آن استفاده میشود به این معنا است که value type ارسالی به آن، توسط ارجاعی از آن، در اختیار متد قرار میگیرد و نه توسط مقدار کپی شدهی آن (حالت پیشفرض) و همچنین متد استفاده کنندهی از آن، مقدار این شیء را تغییر نمیدهد.
واژهی کلیدی in مکمل واژههای کلیدی ref و out است که پیشتر به همراه زبان #C ارائه شده بودند:
- واژهی کلیدی out: مقدار آرگومان مزین شدهی توسط آن، باید درون متد تنظیم شود و صرفا کاربرد ارائهی یک خروجی اضافهتر توسط آن متد را دارد.
- واژهی کلیدی ref: مقدار آرگومان مزین شدهی توسط آن، ممکن است درون متد تنظیم شود، یا خیر و همچنین توسط ارجاع به آن منتقل میشود.
- واژهی کلیدی in: مقدار آرگومان مزین شدهی توسط آن، درون متد تغییر نخواهد کرد و همچنین توسط ارجاع به آن منتقل میشود.
برای مثال اگر پارامترهای value type متد زیر را از نوع in معرفی کنیم، امکان تغییر مقدار آنها درون متد وجود نخواهد داشت:
و کامپایلر با صدور خطای readonly بودن پارامتر number1، از انجام اینکار جلوگیری میکند
واژهی کلیدی جدید in تا چه اندازهای بر روی کارآیی برنامه تاثیر دارد؟
زمانیکه یک value type را به متدی ارسال میکنیم، ابتدا به مکان جدیدی از حافظه کپی شده و سپس مقدار clone شدهی آن، به متد ارسال میشود. با استفاده از واژهی کلیدی in، دقیقا همان ارجاع به مقدار اولیه، به متد ارسال خواهد شد؛ بدون ایجاد کپی اضافهتری از آن. برای بررسی تاثیر این عملیات بر روی کارآیی برنامه، میتوان از BenchmarkDotNet استفاده کرد. برای این منظور ابتدا ارجاعی را به BenchmarkDotNet اضافه میکنیم:
سپس متدهایی را که قرار است کارآیی آنها بررسی شوند، با ویژگی Benchmark مزین خواهیم کرد:
در آخر برای اجرای آن خواهیم داشت:
و در این حالت برنامه را باید توسط دستور «dotnet run -c release» اجرا کرد (اندازه گیری کارآیی در حالت release و نه دیباگ پیشفرض)
با این خروجی نهایی:
همانطور که ملاحظه میکنید، کارآیی برنامه در حالت استفادهی از پارامترهای in، حداقل 3 برابر شدهاست.
امکان استفادهی از واژهی کلیدی in در حین تعریف متدهای الحاقی
در حین تعریف متدهای الحاقی، واژهی کلیدی in باید پیش از واژهی کلیدی this ذکر شود:
در این حالت اگر برنامه را به صورت زیر اجرا کنیم (یکبار با ذکر صریح in، بار دیگر بدون in و یکبار هم به صورت فراخوانی متد الحاقی بر روی عدد):
خروجیهای ذیل را دریافت خواهیم کرد:
به عبارتی حین فراخوانی و استفادهی از متدی که پارامتر آن به صورت in تعریف شدهاست، ذکر in ضروری نیست.
و به طور کلی استفادهی از in در مکانهای ذیل مجاز است:
• methods
• delegates
• lambdas
• local functions
• indexers
• operators
محدودیتهای استفادهی از پارامترهای in
الف) محدودیت استفاده از پارامترهای in در تعریف overloads
مثال زیر را در نظر بگیرید:
در اینجا overloadهای تعریف شدهی متد A تنها در ذکر واژهی کلیدی in یا modifier متفاوت هستند.
اگر سعی کنیم وهلهای از این کلاس را ایجاد کرده و از متدهای A آن استفاده کنیم:
خطای کامپایلر مبهم بودن متد A مورد استفاده صادر خواهد شد. یعنی نمیتوان overload ایی را تعریف کرد که تنها در modifier از نوع in با دیگری متفاوت باشد؛ چون ذکر in در حین فراخوانی متد، اختیاری است.
ب) پارامترهای از نوع in را در متدهای iterator نمیتوان استفاده کرد:
ج) پارامترهای از نوع in را در متدهای async نمیتوان استفاده کرد:
تاثیر کار با متدهای داخلی تغییر دهندهی وضعیت یک struct
مثال زیر را درنظر بگیرید. به نظر شما خروجی آن چیست؟
در اینجا اگر متد TestInStructs.Run را اجرا کنیم، خروجی آن، نمایش عدد 1 خواهد بود.
در ابتدا مقدار struct را به 1 تنظیم و سپس ارجاع آنرا به متدی دیگر که مقدار آنرا به 5 تنظیم میکند، ارسال کردیم. در این حالت برنامه بدون مشکل کامپایل و اجرا میشود. علت اینجا است که کامپایلر #C زمانیکه متدی را در داخل یک struct فراخوانی میکند، یک clone از آن struct را ایجاد کرده و متد را بر روی آن clone اجرا میکند؛ چون نمیداند که آیا این متد وضعیت و مقدار این struct را تغییر میدهد یا خیر. در این حالت کپی اصلی بدون تغییر باقی میماند (در نهایت عدد 1 را مشاهده خواهیم کرد)، اما در آخر فراخوان، ارجاعی از struct را دریافت نکرده و بر روی کپی آن کار میکند. بنابراین مزیت بهبود کارآیی، از دست خواهد رفت.
البته در اینجا اگر میخواستیم مقدار MyValue را مستقیما تغییر دهیم، کامپایلر از آن جلوگیری میکرد و این کد هیچگاه کامپایل نمیشد:
واژهی کلیدی جدید in
C# 7.2، واژهی کلیدی جدیدی را به نام in جهت تعریف پارامترها، معرفی کردهاست. زمانیکه از آن استفاده میشود به این معنا است که value type ارسالی به آن، توسط ارجاعی از آن، در اختیار متد قرار میگیرد و نه توسط مقدار کپی شدهی آن (حالت پیشفرض) و همچنین متد استفاده کنندهی از آن، مقدار این شیء را تغییر نمیدهد.
واژهی کلیدی in مکمل واژههای کلیدی ref و out است که پیشتر به همراه زبان #C ارائه شده بودند:
- واژهی کلیدی out: مقدار آرگومان مزین شدهی توسط آن، باید درون متد تنظیم شود و صرفا کاربرد ارائهی یک خروجی اضافهتر توسط آن متد را دارد.
- واژهی کلیدی ref: مقدار آرگومان مزین شدهی توسط آن، ممکن است درون متد تنظیم شود، یا خیر و همچنین توسط ارجاع به آن منتقل میشود.
- واژهی کلیدی in: مقدار آرگومان مزین شدهی توسط آن، درون متد تغییر نخواهد کرد و همچنین توسط ارجاع به آن منتقل میشود.
برای مثال اگر پارامترهای value type متد زیر را از نوع in معرفی کنیم، امکان تغییر مقدار آنها درون متد وجود نخواهد داشت:
public static int Add(in int number1, in int number2) { number1 = 5; // Cannot assign to variable 'in int' because it is a readonly variable return number1 + number2; }
واژهی کلیدی جدید in تا چه اندازهای بر روی کارآیی برنامه تاثیر دارد؟
زمانیکه یک value type را به متدی ارسال میکنیم، ابتدا به مکان جدیدی از حافظه کپی شده و سپس مقدار clone شدهی آن، به متد ارسال میشود. با استفاده از واژهی کلیدی in، دقیقا همان ارجاع به مقدار اولیه، به متد ارسال خواهد شد؛ بدون ایجاد کپی اضافهتری از آن. برای بررسی تاثیر این عملیات بر روی کارآیی برنامه، میتوان از BenchmarkDotNet استفاده کرد. برای این منظور ابتدا ارجاعی را به BenchmarkDotNet اضافه میکنیم:
<ItemGroup> <PackageReference Include="BenchmarkDotNet" Version="0.10.12" /> </ItemGroup>
using BenchmarkDotNet.Attributes; namespace CS72Tests { public struct Input { public decimal Number1; public decimal Number2; } [MemoryDiagnoser] public class InBenchmarking { const int loops = 50000000; Input inputInstance = new Input(); [Benchmark(Baseline = true)] public decimal RunNormalLoop_Pass_By_Value() { decimal result = 0M; for (int i = 0; i < loops; i++) { result = Run(inputInstance); } return result; } [Benchmark] public decimal RunInLoop_Pass_By_Reference() { decimal result = 0M; for (int i = 0; i < loops; i++) { result = RunIn(in inputInstance); } return result; } public decimal Run(Input input) { return input.Number1; } public decimal RunIn(in Input input) { return input.Number1; } } }
static void Main(string[] args) { var summary = BenchmarkRunner.Run<InBenchmarking>();
با این خروجی نهایی:
Method | Mean | Error | StdDev | Scaled | Allocated | ---------------------------- |----------:|---------:|---------:|-------:|----------:| RunNormalLoop_Pass_By_Value | 280.04 ms | 2.219 ms | 1.733 ms | 1.00 | 0 B | RunInLoop_Pass_By_Reference | 91.75 ms | 1.733 ms | 1.780 ms | 0.33 | 0 B |
امکان استفادهی از واژهی کلیدی in در حین تعریف متدهای الحاقی
در حین تعریف متدهای الحاقی، واژهی کلیدی in باید پیش از واژهی کلیدی this ذکر شود:
public static class Factorial { public static int Calculate(in this int num) { int result = 1; for (int i = num; i > 1; i--) result *= i; return result; } }
int num = 3; Console.WriteLine($"(in num) -> {Factorial.Calculate(in num)}"); Console.WriteLine($"(num) -> {Factorial.Calculate(num)}"); Console.WriteLine($"num. -> {num.Calculate()}");
(in num) -> 6 (num) -> 6 num. -> 6
و به طور کلی استفادهی از in در مکانهای ذیل مجاز است:
• methods
• delegates
• lambdas
• local functions
• indexers
• operators
محدودیتهای استفادهی از پارامترهای in
الف) محدودیت استفاده از پارامترهای in در تعریف overloads
مثال زیر را در نظر بگیرید:
public class CX { public void A(Input a) { Console.WriteLine("int a"); } public void A(in Input a) { Console.WriteLine("in int a"); } }
اگر سعی کنیم وهلهای از این کلاس را ایجاد کرده و از متدهای A آن استفاده کنیم:
public class Y { public void Test() { var inputInstance = new Input(); var cx = new CX(); cx.A(inputInstance); // The call is ambiguous between the following methods or properties: 'CX.A(Input)' and 'CX.A(in Input)' } }
ب) پارامترهای از نوع in را در متدهای iterator نمیتوان استفاده کرد:
public IEnumerable<int> B(in int a) // Iterators cannot have ref or out parameters { Console.WriteLine("in int a"); yield return 1; }
ج) پارامترهای از نوع in را در متدهای async نمیتوان استفاده کرد:
public async Task C(in int a) // Async methods cannot have ref or out parameters { await Task.Delay(1000); }
تاثیر کار با متدهای داخلی تغییر دهندهی وضعیت یک struct
مثال زیر را درنظر بگیرید. به نظر شما خروجی آن چیست؟
using System; namespace CS72Tests { struct MyStruct { public int MyValue { get; set; } public void UpdateMyValue(int value) { MyValue = value; } } public static class TestInStructs { public static void Run() { var myStruct = new MyStruct(); myStruct.UpdateMyValue(1); UpdateMyValue(myStruct); Console.WriteLine(myStruct.MyValue); } static void UpdateMyValue(in MyStruct myStruct) { myStruct.UpdateMyValue(5); } } }
در ابتدا مقدار struct را به 1 تنظیم و سپس ارجاع آنرا به متدی دیگر که مقدار آنرا به 5 تنظیم میکند، ارسال کردیم. در این حالت برنامه بدون مشکل کامپایل و اجرا میشود. علت اینجا است که کامپایلر #C زمانیکه متدی را در داخل یک struct فراخوانی میکند، یک clone از آن struct را ایجاد کرده و متد را بر روی آن clone اجرا میکند؛ چون نمیداند که آیا این متد وضعیت و مقدار این struct را تغییر میدهد یا خیر. در این حالت کپی اصلی بدون تغییر باقی میماند (در نهایت عدد 1 را مشاهده خواهیم کرد)، اما در آخر فراخوان، ارجاعی از struct را دریافت نکرده و بر روی کپی آن کار میکند. بنابراین مزیت بهبود کارآیی، از دست خواهد رفت.
البته در اینجا اگر میخواستیم مقدار MyValue را مستقیما تغییر دهیم، کامپایلر از آن جلوگیری میکرد و این کد هیچگاه کامپایل نمیشد:
static void UpdateMyValue(in MyStruct myStruct) { myStruct.MyValue = 5; // Cannot assign to a member of variable 'in MyStruct' because it is a readonly variable myStruct.UpdateMyValue(5); }
اشتراکها
ویدیوهای کنفرانس NDC 2014
باید عرض کنم بله وقتی کاربری یافت نشد چه کاری میتوانیم انجام دهیم؟
مطلب پایهای در مورد صدور استثناءها در اینجا هست: Dont Use Exceptions For Flow Control و قسمتی از آن به این صورت است:
Errors should be handled via exceptions, but successes shouldn't
مشکلی که در اینجا هست این است که بازگشت null یک کاربر در یک کوئری، یک error نیست؛ یک بازگشت و عملیات موفقیت آمیز است. به همین جهت صدور استثناء برای دریافت آن نباید رخ دهد:
The typical meaning of the word "exception" is an event that is unexpected rather than part of normal operation; otherwise people would just say "event"
و اینکه نتیجهی یک متد باید در همان متد به صورت استثناء درنظر گرفته شود، غیرضروری است و این تصمیم گیری باید به عهدهی فراخوان گذاشته شود. گاهی میخواهیم بررسی کنیم کاربری در دیتابیس هست؟ اگر بله، آنرا تکراری ثبت نکنیم. گاهی میخواهیم بررسی کنیم اگر در دیتابیس هست، اطلاعات او را به روز رسانی کنیم. اینکه کوئری مدنظر نال بر میگرداند، تصمیم گیری در مورد اهمیت آن ربطی به آن متد ندارد و صرفا به عهدهی فراخوان است:
Methods should have a way to indicate various kinds of results. Whether those results are treated as exceptional should depend on the caller.
مدیریت حافظه، نقش مهمی را در برنامه نویسی ایفا میکند و بر عملکرد و کارآیی یک برنامه تاثیر میگذارد. این مقاله، مروری را بر سه نوع حافظهی اصلی ارائه میکند: static memory, stack memory, heap . درک تفاوت بین این انواع حافظهها میتواند به شما در بهینه سازی کد و جلوگیری از مشکلات احتمالی، کمک کند.
Static Memory
حافظهی static برای ذخیرهی باینریهای برنامه، متغیرهای استاتیک و حروف رشتهای (در Rust) استفاده میشود. اندازهی حافظه استاتیک ثابت است و در زمان کامپایل مشخص میشود. حافظهی استاتیک طول عمری برابر با عمر برنامه دارد و مقادیر آن از شروع، تا پایان برنامه، باقی میماند. پاکسازی حافظهی استاتیک به صورت خودکار انجام میشود و با پایان برنامه انجام میشود.
مواردی که در حافظه استاتیک قرار میگیرند :
Size :
Fixed ( محاسبه در زمان کامپایل )
Lifetime : برابر با طول عمر برنامه
پاکسازی : به صورت خودکار ؛ زمانی که برنامه متوقف میشود .
Stack Memory
حافظهی پشته، مسئول نگهداری آرگومانهای تابع و متغیرهای محلی است. پشته، شامل stack frames است که برای هر فراخوانی تابع در زنجیرهای از فراخوانیهای تابع، ایجاد میشوند (به عنوان مثال، A B را فرا میخواند، B C را فرا میخواند). حافظهی پشته به اندازهی مشخصی در زمان کامپایل نیاز دارد؛ به این معنا که آرگومانها و متغیرهای درون stack frames باید اندازههای از پیش تعیین شدهای داشته باشند. اندازهی پشته، پویا است؛ اما دارای حد بالایی ثابتی است که در هنگام راه اندازی برنامه تعریف شدهاست. حافظهی پشته، دارای طول عمری برابر با طول عمر عملکرد است و هنگامیکه عملکرد، نتیجهای را بر میگرداند، پاکسازی آن خودکار است.
بیایید نگاهی به یک مثال ساده در Rust بیندازیم تا حافظهی پشته را بهتر درک کنیم:
در این برنامهی Rust، دو عملکرد add و main را داریم. هنگامیکه برنامه شروع به اجرا میکند، یک stack frames برای تابع اصلی در حافظهی پشته ایجاد میشود. این stack frames شامل متغیرهای محلی a، b و فراخوانی تابع برای add(a, b) است.
هنگامیکه تابع add فراخوانی میشود، یک stack frames دیگر در بالای stack frames main موجود ایجاد میشود. این stack frames جدید حاوی متغیرهای محلی x، y و sum است. مقادیر a و b به عنوان آرگومان به تابع add ارسال میشوند و به ترتیب در x و y ذخیره میشوند. پس از محاسبهی مجموع، تابع add، مقداری را بر میگرداند و stack frames آن به طور خودکار از حافظهی پشته حذف میشود.
سپس تابع main، مقدار برگشتی را از تابع add دریافت میکند و به نتیجهی متغیر اختصاص مییابد. از ماکروی println! برای چاپ نتیجه استفاده میشود. پس از اتمام اجرای برنامه و بازگشت تابع اصلی، stack frames آن نیز از حافظهی پشته حذف میشود و حافظه بهطور خودکار پاک میشود.
در این مثال، میتوانید ببینید که چگونه از stack frames برای ذخیرهی آرگومانهای تابع و متغیرهای محلی در Rust استفاده میشود. اندازهی این متغیرها در زمان کامپایل مشخص میشود و طول عمر حافظهی پشته، برابر با طول عمر تابع است. هنگامیکه تابع برمیگردد، فرآیند پاکسازی آن خودکار است و قاب پشتهی مربوطه را حذف میکند.
Heap Memory
حافظهی Heap، مقادیری را ذخیره میکند که باید فراتر از طول عمر یک تابع مانند مقادیر بزرگ و مقادیر قابل دسترسی توسط رشتههای متعدد، زنده بمانند. از آنجائیکه هر رشته دارای پشتهی مخصوص به خود است، همهی آنها یک پشتهی مشترک دارند. حافظهی Heap میتواند مقادیری با اندازهی ناشناخته را در زمان کامپایل، در خود جای دهد؛ مانند رشتههای ورودی کاربر. اندازهی پشته نیز پویا است؛ با حد بالایی ثابت که در زمان راه اندازی برنامه تعیین میشود. حافظهی Heap طول عمری دارد که توسط برنامه نویس تعیین میشود و برنامه نویس تصمیم میگیرد که چه زمانی باید حافظه تخصیص داده شود. پاکسازی حافظهی هیپ به صورت دستی است و نیاز به مداخلهی برنامه نویس دارد.
در این مثال ساده، روش استفاده از حافظهی پشته نشان داده میشود:
در این برنامهی Rust، یک ساختار LargeData را تعریف میکنیم که حاوی <Vec<i32 است. این روش جدید، یک شیء LargeData را به اندازهی مشخصی مقداردهی اولیه میکند. در تابع main، یک شیء LargeData را با اندازه (1,000,000 عنصر) ایجاد میکنیم و با استفاده از Rc::new روی پشته ذخیره میکنیم. Rc یک اشارهگر شمارش مرجع است که به چندین متغیر اجازه میدهد تا مالکیت دادههای تخصیص داده شده را به اشتراک بگذارند (در ادامهی دوره توضیح داده خواهد شد).
سپس دو متغیر دیگر را به نامهای shared_data1 و shared_data2 ایجاد میکنیم که با استفاده از Rc::clone، یک شیء LargeData تخصیصیافتهی مشابه را به اشتراک میگذارند. این نشان میدهد که چگونه حافظهی پشته را میتوان در بین متغیرهای متعددی به اشتراک گذاشت؛ حتی فراتر از طول عمر تابع اصلی که داده را ایجاد کرده است.
در این مثال، پاکسازی حافظهی پشته به طور خودکار توسط مکانیزم شمارش مرجع Rust مدیریت میشود (در ادامهی دوره توضیح داده خواهد شد). هنگامیکه تعداد مرجع نشانگر Rc به صفر میرسد (یعنی وقتی همهی متغیرهایی که دادهها را به اشتراک میگذارند از محدوده خارج میشوند)، حافظهی تخصیص داده شده، روی پشته تخصیص داده میشود.
این مثال نشان میدهد که چگونه میتوان از حافظهی پشته برای ذخیرهی ساختارهای داده یا مقادیر بزرگی استفاده کرد که باید بیشتر از طول عمر یک تابع باشند و چگونه میتوان حافظهی پشته را بین چندین متغیر به اشتراک گذاشت.
Static Memory
حافظهی static برای ذخیرهی باینریهای برنامه، متغیرهای استاتیک و حروف رشتهای (در Rust) استفاده میشود. اندازهی حافظه استاتیک ثابت است و در زمان کامپایل مشخص میشود. حافظهی استاتیک طول عمری برابر با عمر برنامه دارد و مقادیر آن از شروع، تا پایان برنامه، باقی میماند. پاکسازی حافظهی استاتیک به صورت خودکار انجام میشود و با پایان برنامه انجام میشود.
مواردی که در حافظه استاتیک قرار میگیرند :
- Program Binary
- Static variables
- String Literals (in Rust)
Size :
Fixed ( محاسبه در زمان کامپایل )
Lifetime : برابر با طول عمر برنامه
پاکسازی : به صورت خودکار ؛ زمانی که برنامه متوقف میشود .
حافظهی پشته، مسئول نگهداری آرگومانهای تابع و متغیرهای محلی است. پشته، شامل stack frames است که برای هر فراخوانی تابع در زنجیرهای از فراخوانیهای تابع، ایجاد میشوند (به عنوان مثال، A B را فرا میخواند، B C را فرا میخواند). حافظهی پشته به اندازهی مشخصی در زمان کامپایل نیاز دارد؛ به این معنا که آرگومانها و متغیرهای درون stack frames باید اندازههای از پیش تعیین شدهای داشته باشند. اندازهی پشته، پویا است؛ اما دارای حد بالایی ثابتی است که در هنگام راه اندازی برنامه تعریف شدهاست. حافظهی پشته، دارای طول عمری برابر با طول عمر عملکرد است و هنگامیکه عملکرد، نتیجهای را بر میگرداند، پاکسازی آن خودکار است.
بیایید نگاهی به یک مثال ساده در Rust بیندازیم تا حافظهی پشته را بهتر درک کنیم:
fn add(x: i32, y: i32) -> i32 { let sum = x + y; sum } fn main() { let a = 5; let b = 3; let result = add(a, b); println!("The sum is: {}", result); }
هنگامیکه تابع add فراخوانی میشود، یک stack frames دیگر در بالای stack frames main موجود ایجاد میشود. این stack frames جدید حاوی متغیرهای محلی x، y و sum است. مقادیر a و b به عنوان آرگومان به تابع add ارسال میشوند و به ترتیب در x و y ذخیره میشوند. پس از محاسبهی مجموع، تابع add، مقداری را بر میگرداند و stack frames آن به طور خودکار از حافظهی پشته حذف میشود.
سپس تابع main، مقدار برگشتی را از تابع add دریافت میکند و به نتیجهی متغیر اختصاص مییابد. از ماکروی println! برای چاپ نتیجه استفاده میشود. پس از اتمام اجرای برنامه و بازگشت تابع اصلی، stack frames آن نیز از حافظهی پشته حذف میشود و حافظه بهطور خودکار پاک میشود.
در این مثال، میتوانید ببینید که چگونه از stack frames برای ذخیرهی آرگومانهای تابع و متغیرهای محلی در Rust استفاده میشود. اندازهی این متغیرها در زمان کامپایل مشخص میشود و طول عمر حافظهی پشته، برابر با طول عمر تابع است. هنگامیکه تابع برمیگردد، فرآیند پاکسازی آن خودکار است و قاب پشتهی مربوطه را حذف میکند.
Heap Memory
حافظهی Heap، مقادیری را ذخیره میکند که باید فراتر از طول عمر یک تابع مانند مقادیر بزرگ و مقادیر قابل دسترسی توسط رشتههای متعدد، زنده بمانند. از آنجائیکه هر رشته دارای پشتهی مخصوص به خود است، همهی آنها یک پشتهی مشترک دارند. حافظهی Heap میتواند مقادیری با اندازهی ناشناخته را در زمان کامپایل، در خود جای دهد؛ مانند رشتههای ورودی کاربر. اندازهی پشته نیز پویا است؛ با حد بالایی ثابت که در زمان راه اندازی برنامه تعیین میشود. حافظهی Heap طول عمری دارد که توسط برنامه نویس تعیین میشود و برنامه نویس تصمیم میگیرد که چه زمانی باید حافظه تخصیص داده شود. پاکسازی حافظهی هیپ به صورت دستی است و نیاز به مداخلهی برنامه نویس دارد.
در این مثال ساده، روش استفاده از حافظهی پشته نشان داده میشود:
use std::rc::Rc; #[derive(Debug)] struct LargeData { data: Vec<i32>, } impl LargeData { fn new(size: usize) -> LargeData { LargeData { data: vec![0; size], } } } fn main() { let large_data = Rc::new(LargeData::new(1_000_000)); let shared_data1 = Rc::clone(&large_data); let shared_data2 = Rc::clone(&large_data); println!("{:?}", shared_data1); println!("{:?}", shared_data2); }
سپس دو متغیر دیگر را به نامهای shared_data1 و shared_data2 ایجاد میکنیم که با استفاده از Rc::clone، یک شیء LargeData تخصیصیافتهی مشابه را به اشتراک میگذارند. این نشان میدهد که چگونه حافظهی پشته را میتوان در بین متغیرهای متعددی به اشتراک گذاشت؛ حتی فراتر از طول عمر تابع اصلی که داده را ایجاد کرده است.
در این مثال، پاکسازی حافظهی پشته به طور خودکار توسط مکانیزم شمارش مرجع Rust مدیریت میشود (در ادامهی دوره توضیح داده خواهد شد). هنگامیکه تعداد مرجع نشانگر Rc به صفر میرسد (یعنی وقتی همهی متغیرهایی که دادهها را به اشتراک میگذارند از محدوده خارج میشوند)، حافظهی تخصیص داده شده، روی پشته تخصیص داده میشود.
این مثال نشان میدهد که چگونه میتوان از حافظهی پشته برای ذخیرهی ساختارهای داده یا مقادیر بزرگی استفاده کرد که باید بیشتر از طول عمر یک تابع باشند و چگونه میتوان حافظهی پشته را بین چندین متغیر به اشتراک گذاشت.
نظرات مطالب
Span در C# 7.2
In order to cleanse the data as we parse it, we thought using a try/catch would be ok. If we don’t catch the exceptions, we’re good, right?
Turns out it kills our performance when we throw a lot of exceptions, even if we don’t catch them. Each exception has some costs . We needed to find a way to handle this data without involving exceptions.
TryParse turns out to be a method designed to solve our problem. We ran some benchmarks to prove it.
نظرات مطالب
ثبت استثناهای مدیریت شده توسط ELMAH
در مطلب «نکات کار با استثناءها در دات نت» به این موارد بهتر پرداخته شدهاست:
«... در واقع استثناها حالتهایی هستند که غیرقابل پیشبینی هستند. این حالتها میتوانند یک خطای منطقی از طرف برنامهنویس و یا چیزی خارج کنترل برنامهنویس باشند (مانند خطاهای سیستمعامل، شبکه، دیسک). یعنی در بیشتر مواقع این نوع خطاها را نمیتوان مدیریت کرد ...»
و یا
« ... در واقع استثناءها بستگی به حالتهای مختلفی دارد. در مثال اول وجود فایل حیاتی است ولی در حالت دوم بدون وجود فایل نیز برنامه میتواند به کار خود ادامه داده و فایل مورد نظر را از نو ایجاد کند ...»
بنابراین «حیاتی بودن» یک شرط در حال بررسی، معیاری هست برای صدور استثناء یا مدیریت آن. اگر حیاتی است، باید در همان نقطه کار خاتمه پیدا کند، استثناء مدیریت نشود و یا استثنایی مشخص صادر شود ( fail fast ).
«... در واقع استثناها حالتهایی هستند که غیرقابل پیشبینی هستند. این حالتها میتوانند یک خطای منطقی از طرف برنامهنویس و یا چیزی خارج کنترل برنامهنویس باشند (مانند خطاهای سیستمعامل، شبکه، دیسک). یعنی در بیشتر مواقع این نوع خطاها را نمیتوان مدیریت کرد ...»
و یا
« ... در واقع استثناءها بستگی به حالتهای مختلفی دارد. در مثال اول وجود فایل حیاتی است ولی در حالت دوم بدون وجود فایل نیز برنامه میتواند به کار خود ادامه داده و فایل مورد نظر را از نو ایجاد کند ...»
بنابراین «حیاتی بودن» یک شرط در حال بررسی، معیاری هست برای صدور استثناء یا مدیریت آن. اگر حیاتی است، باید در همان نقطه کار خاتمه پیدا کند، استثناء مدیریت نشود و یا استثنایی مشخص صادر شود ( fail fast ).