اشتراکها
NET Core. چیست؟
برای اغلب توسعه دهندههای دات نت (برنامههای وب و دسکتاپ) تنها یک دات نت فریم ورک شناخته شده وجود دارد: The `Full` .NET Framework
که تنها بر روی ویندوز قابل اجرا است و آخرین نگارش پایدار آن در زمان نگارش این مطلب، 4.6.1 است. این فریم ورک بزرگ، از اجزایی تشکیل شدهاست که در تصویر ذیل قابل مشاهدهاند:
مهمترین قسمتهای این فریم ورک «بزرگ» شامل مواردی مانند CLR که کار تبدیل کدهای IL را به کدهای ماشین انجام میدهد، BCL که کلاسهای پایهای را جهت کار با IO، Text و غیره، فراهم میکنند، هستند؛ به علاوه کتابخانههایی مانند Windows Forms، WPF و ASP.NET که برفراز BCL و CLR کار میکنند.
هرچند تعدادی از توسعه دهندههای دات نت تنها با Full framework کار میکنند، اما در طی سالهای اخیر انشعابات بسیار دیگری از آن به وجود آمدهاند؛ مانند دات نتهای ویژهی ویندوزهای 8 و Universal Windows Platform، دات نت مخصوص ویندوز فون 8 و ویندوز فون مبتنی بر پلتفرم سیلورلایت، به علاوه دات نت پلتفرم زامارین برای توسعهی برنامههای iOS و Android نیز هم اکنون وجود دارند (البته در اینجا Mono، دات نت میکرو و غیره را هم باید ذکر کرد). این فریم ورکها و انشعابات، به همراه پیاده سازی یک سری موارد مشترک و مواردی کاملا اختصاصی هستند که به سایر پلتفرمهای دیگر قابل انتقال نیستند.
با زیاد شدن تعداد انشعابات دات نت «بزرگ»، نوشتن کدی که قابل اجرای بر روی تمام پلتفرمهای یاد شده باشد، مشکل شد. اینجا بود که مفهومی را به نام PCL یا Portable class libraries معرفی کردند:
هدف از PCLها، ساده سازی کامپایل و به اشتراک گذاری کد بین پلتفرمهای مختلف بود و پشتیبانی قابل توجهی هم از آن در VS.NET وجود دارد. هرچند این روش نسبتا موفق بود اما مشکلاتی را هم به همراه داشت. برای مثال با ارائهی یک انشعاب و پلتفرم دیگری از دات نت «بزرگ»، کتابخانهی PCL موجود، باید برای این انشعاب جدید مجددا کامپایل شود. به علاوه در اینجا تنها محدود به انتخاب امکانات مشترک بین پلتفرمهای مختلف هستید.
برای رفع این مشکلات در پایان سال 2014، یک «دات نت فریم ورک جدید» به نام NET Core. معرفی شد که سورس باز است و همچنین چندسکویی (از ویندوز، لینوکس و OSX پشتیبانی میکند).
هرچند پیشتر Windows Store و ASP.NET Core app به صورت پلتفرمهایی مجزا ارائه شده بودند، اما اکنون از یک BCL مشترک به نام CoreFX استفاده میکنند و نحوهی توزیع آنها صرفا از طریق نیوگت است. به عبارتی اینبار بجای دریافت یک فریم ورک «بزرگ»، تنها اجزایی را دریافت میکنید که از طریق نیوگت سفارش دادهاید.
به این ترتیب نه تنها کار توزیع برنامههای مبتنی بر NET Core. با سهولت بیشتری انجام خواهد شد، بلکه به روز رسانی اجزای یک برنامه، تاثیری بر روی سایر برنامهها نخواهد داشت و مشکلات جانبی را به وجود نمیآورد. به علاوه دیگر نیازی نیست تا منتظر یک نگارش «بزرگ» دیگر باشید تا بتوانید آخرین به روز رسانیها را دریافت کنید. اینبار به روز رسانی بستههای نیوگت برنامه معادل هستند با به روز رسانی کل فریم ورک در نگارشهای قبلی «بزرگ» آن. در اینجا حتی CoreCLR و NET Native runtime. که مربوط به Windows runtime است هم از طریق نیوگت به روز رسانی میشود.
البته NET Core. انتهای مسیر نیست و هم اکنون NETStandard نیز جهت رفع مشکلات کامپایل مجدد PCLها در حال توسعه است و پس از ارائهی آن، PCLها منسوخ شده درنظر گرفته میشوند. در این حالت با انتخاب target platform ایی به نام NETStandard (بجای مثلا انتخاب دات نت 4.5 و ویندوز فون 8)، اینبار دات نت 4.5 و ویندوز فون 8 و تمام پلتفرمهای دیگر، به صورت یکجا انتخاب میشوند و اگر پلتفرم جدیدی برای مثال از NETStandard نگارش 1.1 پشتیبانی کند، به این معنا است که کتابخانهی شما هم اکنون با آن سازگار است و دیگر نیازی به کامپایل مجدد آن نخواهد بود.
به علاوه هر برنامهای که بر اساس NETStandard تهیه شود، قابلیت اجرای بر روی NET Core. را نیز خواهد داشت. به عبارتی برنامههای NETStandard همان برنامههای مبتنی بر NET Core. هستند.
ASP.NET Core چیست؟
در زمان نگارش این مطلب، دو گزینهی برنامههای وب ASP.NET Core 1.0 و همچنین Windows Store apps (مبتنی بر NET Native Runtime.) قابلیت استفادهی از این پلتفرم جدید NET Core. دارند.
ASP.NET Core 1.0، که پیشتر با نام ASP.NET 5 معرفی شده بود، بازنویسی کامل ASP.NET است که با ایدهی کاملا ماژولار بودن، تهیه شدهاست و از طریق آن، قابلیت به روز رسانی منظم و توزیع آسان از طریق نیوگت، میسر خواهد شد. به علاوه در آن، بسیاری از الگوهای برنامه نویسی شیءگرا مانند تزریق وابستگیها، به صورت توکار و از ابتدا پشتیبانی میشوند.
ASP.NET Core 1.0 از WebForms ، VB ، WebPages و SignalR پشتیبانی نمیکند. البته در این بین عدم پشتیبانی از «وب فرمها» قطعی است؛ اما افزودن سه مورد دیگر یاد شده، جزو لیست کارهای پس از ارائهی نگارش 1 این فریم ورک قرار دارند و به زودی ارائه خواهند شد.
اکنون وضعیت ASP.NET MVC 5 و ASP.NET Web API 2 چگونه است؟
ASP.NET Core 1.0 مدل برنامه نویسی ASP.NET MVC و Web API را به صورت یکپارچه ارائه میدهد و دیگر خبری از ارائهی مجزای اینها نخواهد بود و دقیقا بر مبنای مفاهیم برنامه نویسی این دو بنا شدهاست. به صورت خلاصه MVC + Web API + Web Pages = Core MVC 1.0
پیشتر فضای نام System.Web.MVC مخصوص ASP.NET MVC بود و فضای نام مجزای دیگری به نام System.Web.Http مخصوص ASP.NET Web API. اما اکنون تنها یک فضای نام مشترک و یکپارچه به نام Microsoft.AspNet.Mvc هر دوی اینها را پوشش میدهد.
در این نگارش جدید وابستگی از system.web مبتنی بر IIS حذف شدهاست و با استفاده از هاست جدید چندسکویی به نام Kesterl، به سرعتی 5 برابر سرعت NodeJS دست یافتهاند.
آخرین تاریخ به روز رسانی ASP.NET MVC 5.x دوشنبه، 20 بهمن 1393 است (با ارائه نگارش 5.2.3 که آخرین نگارش رسمی و پایدار آن است) و آخرین تاریخ به روز رسانی ASP.NET Web API 2.x نیز همان روز است.
هرچند مایکروسافت عادت به اعلام رسمی پایان پشتیبانی از بسیاری از محصولات خود را ندارد اما تمام فناوریهای «قدیمی» خودش را بر روی CodePlex نگهداری میکند و تمام فناوریهای «جدید» را به GitHub منتقل کردهاست. بنابراین اگر در مورد فناوری خاصی به Codeplex رسیدید، یعنی «دیگر ادامهی رسمی نخواهد یافت» و حداکثر در حد رفع یک سری باگها و مشکلات گزارش شده باقی میمانند.
مثال 1: هم اکنون نگارش دوم ASP.NET Identity را بر روی Codeplex میتوانید مشاهده کنید. نگارش سوم آن به GitHub منتقل شدهاست که این نگارش صرفا با ASP.NET Core 1.0 سازگار است. در مورد ASP.NET MVC و Web API نیز چنین حالتی رخ دادهاست. نگارشهای 5 و 2 آنها بر روی Codeplex موجود هستند و نگارش ششم که به ASP.NET Core 1.0 تغییر نام یافت و ترکیبی است از MVC و Web API، در GitHub توسعه مییابد.
مثال 2: WCF به علت پیچیدگی بیش از حد و مدرن نبودن طراحی آن، رقابت را به ASP.NET Web API 2.x واگذار کرد و مدل برنامه نویسی ASP.NET Web API 2.x نیز هم اکنون جزئی از ASP.NET Core 1.0 است. بنابراین اگر قصد ایجاد پروژهی جدیدی را بر این مبنا دارید، بهتر است با APS.NET Core 1.0 کار را شروع کنید.
اما هنوز تعداد زیادی از کتابخانههای Full framework به NET Core. انتقال پیدا نکردهاند
برای نمونه هنوز EF Core 1.0 که پیشتر نام EF 7.x به آن داده شده بود، به مرحلهی نهایی تکمیل قابلیتهای آن نرسیدهاست. اما باید دانست که ASP.NET Core 1.0 صرفا بر فراز NET Core. قابل اجرا نیست؛ بلکه قابلیت اجرای بر فراز NET 4.6. و یا همان دات نت «بزرگ و کامل» را نیز دارد. بنابراین به سادگی قابلیت اجرای EF 6.x و یا NHibernate را نیز دارا است. تنها مزیتی را که در اینجا از دست خواهید، قابلیت چندسکویی بودن ASP.NET Core 1.0 است؛ زیرا EF 6.x با چنین دیدی طراحی نشدهاست.
همانطور که ملاحظه میکنید، ASP.NET Core 1.0 قابلیت اجرای بر روی هر دوی NET Core 1.0. و NET 4.6. را دارا است. اما یکی، چندسکویی است و دیگری صرفا مختص به ویندوز.
فناورهای منسوخ شدهی در NET Core.
یکسری از فناوریها و کتابخانهها احتمالا هیچگاه قابلیت انتقال به NET Core. را نخواهند یافت و یا حداقل باید تا چندنگارش بعدی آن صبر کنند. فناوریهای خاتمه یافتهی با NET Core. به شرح زیر هستند:
- Reflection: همانطور که عنوان شد، NET Core. بر فراز CoreCLR و همچنین NET Native runtime. اجرا میشود و تولید برنامههای native و static linking آنها مانند برنامههای ++C، نیاز به دانستن اجزایی دارد که به صورت پویا فراخوانی نمیشوند و بلافاصله و در زمان کامپایل، توسط کامپایلر قابل تشخیص هستند. همین محدودیت سبب شدهاست که استفادهی از Reflection در NET Core. به حداقل ممکن آن برسد. برای مثال در System.Object متد GetType آن تنها نام نوع را باز میگرداند و نه اطلاعات بیشتری را مانند GetMembers سابق.
- App Domains: هرچند CoreCLR از App Domains پشتیبانی میکند اما NET Native runtime. خیر. به همین جهت برای ایزوله سازی برنامهها توصیه شدهاست که از containerهایی مانند docker استفاده شود.
- Remoting: پیش از WCF جهت برقراری ارتباط بین برنامهها مطرح شده بود و هم اکنون در دات نت کامل هم آنچنان استفادهای از آن نمیشود.
- binary serialization: البته کتابخانههایی مانند JSON.NET و امثال آن، نگارش NET Core. هم دارند؛ اما چون binary serialization نیاز به اطلاعات reflection قابل توجهی دارد دیگر پشتیبانی نخواهد شد.
فناورهایی که به زودی به NET Core. منتقل میشوند
یکسری از فناوریها مانند XAML هنوز معادل NET Core. ندارند و لیست زیر قرار است از طرف مایکروسافت سورس باز شده و همچنین به NET Core. منتقل شود:
System.Data
System.DirectoryServices
System.Drawing
System.Transactions
System.Xml.Xsl and System.Xml.Schema
System.Net.Mail
System.IO.Ports
System.Workflow
System.Xaml
مراحل نصب ASP.NET Core 1.0
پیش از نصب نگارش 1.0 RTM باید به این نکته دقت داشت که نصاب آن، نگارشهای آزمایشی قبلی را حذف و یا بازنویسی نمیکند و همین مساله ممکن است سبب بروز تداخلهایی و یا حتی از کار افتادن VS.NET شما شود. بنابراین اگر نگارشهای RC یا بتا را پیشتر نصب کردهاید، به Add remove programs ویندوز مراجعه کرده و سه مورد ذیل را حتما حذف کنید (خیلی مهم):
- Preview Tooling (all versions)
- NET Core Runtime SDK (all versions).
- NET Core Runtime (all Versions).
پس از حذف بستههای قدیمی، برای نصب نگارش 1.0 RTM، ابتدا نیاز است Visual Studio 2015 Update 3 را نصب کنید و پس از آن با استفاده از NET Core for Visual Studio Official MSI Installer. کار نصب اجزای مورد نیاز آن انجام خواهد شد.
بررسی شماره نگارش 1.0 RTM
پس از نصب اجزای عنوان شده، خط فرمان را گشوده و دستور ذیل را صادر کنید:
همانطور که مشاهده میکنید، نگارش ذکر شده هنوز در مرحلهی preview است و صرفا مرتبط است به tooling و یا ابزارهای مرتبط با آن.
اگر یک پروژهی خالی ASP.NET Core Web Application را نیز شروع کنید (با طی مراحل زیر جهت ایجاد یک پروژهی جدید):
در اینجا فایل جدیدی را به نام global.json مشاهده میکنید که محتوایات آن شامل دقیقا همین شماره نگارش است؛ به همراه معرفی پوشههای اصلی پروژه:
برای اغلب توسعه دهندههای دات نت (برنامههای وب و دسکتاپ) تنها یک دات نت فریم ورک شناخته شده وجود دارد: The `Full` .NET Framework
که تنها بر روی ویندوز قابل اجرا است و آخرین نگارش پایدار آن در زمان نگارش این مطلب، 4.6.1 است. این فریم ورک بزرگ، از اجزایی تشکیل شدهاست که در تصویر ذیل قابل مشاهدهاند:
مهمترین قسمتهای این فریم ورک «بزرگ» شامل مواردی مانند CLR که کار تبدیل کدهای IL را به کدهای ماشین انجام میدهد، BCL که کلاسهای پایهای را جهت کار با IO، Text و غیره، فراهم میکنند، هستند؛ به علاوه کتابخانههایی مانند Windows Forms، WPF و ASP.NET که برفراز BCL و CLR کار میکنند.
هرچند تعدادی از توسعه دهندههای دات نت تنها با Full framework کار میکنند، اما در طی سالهای اخیر انشعابات بسیار دیگری از آن به وجود آمدهاند؛ مانند دات نتهای ویژهی ویندوزهای 8 و Universal Windows Platform، دات نت مخصوص ویندوز فون 8 و ویندوز فون مبتنی بر پلتفرم سیلورلایت، به علاوه دات نت پلتفرم زامارین برای توسعهی برنامههای iOS و Android نیز هم اکنون وجود دارند (البته در اینجا Mono، دات نت میکرو و غیره را هم باید ذکر کرد). این فریم ورکها و انشعابات، به همراه پیاده سازی یک سری موارد مشترک و مواردی کاملا اختصاصی هستند که به سایر پلتفرمهای دیگر قابل انتقال نیستند.
با زیاد شدن تعداد انشعابات دات نت «بزرگ»، نوشتن کدی که قابل اجرای بر روی تمام پلتفرمهای یاد شده باشد، مشکل شد. اینجا بود که مفهومی را به نام PCL یا Portable class libraries معرفی کردند:
هدف از PCLها، ساده سازی کامپایل و به اشتراک گذاری کد بین پلتفرمهای مختلف بود و پشتیبانی قابل توجهی هم از آن در VS.NET وجود دارد. هرچند این روش نسبتا موفق بود اما مشکلاتی را هم به همراه داشت. برای مثال با ارائهی یک انشعاب و پلتفرم دیگری از دات نت «بزرگ»، کتابخانهی PCL موجود، باید برای این انشعاب جدید مجددا کامپایل شود. به علاوه در اینجا تنها محدود به انتخاب امکانات مشترک بین پلتفرمهای مختلف هستید.
برای رفع این مشکلات در پایان سال 2014، یک «دات نت فریم ورک جدید» به نام NET Core. معرفی شد که سورس باز است و همچنین چندسکویی (از ویندوز، لینوکس و OSX پشتیبانی میکند).
هرچند پیشتر Windows Store و ASP.NET Core app به صورت پلتفرمهایی مجزا ارائه شده بودند، اما اکنون از یک BCL مشترک به نام CoreFX استفاده میکنند و نحوهی توزیع آنها صرفا از طریق نیوگت است. به عبارتی اینبار بجای دریافت یک فریم ورک «بزرگ»، تنها اجزایی را دریافت میکنید که از طریق نیوگت سفارش دادهاید.
به این ترتیب نه تنها کار توزیع برنامههای مبتنی بر NET Core. با سهولت بیشتری انجام خواهد شد، بلکه به روز رسانی اجزای یک برنامه، تاثیری بر روی سایر برنامهها نخواهد داشت و مشکلات جانبی را به وجود نمیآورد. به علاوه دیگر نیازی نیست تا منتظر یک نگارش «بزرگ» دیگر باشید تا بتوانید آخرین به روز رسانیها را دریافت کنید. اینبار به روز رسانی بستههای نیوگت برنامه معادل هستند با به روز رسانی کل فریم ورک در نگارشهای قبلی «بزرگ» آن. در اینجا حتی CoreCLR و NET Native runtime. که مربوط به Windows runtime است هم از طریق نیوگت به روز رسانی میشود.
البته NET Core. انتهای مسیر نیست و هم اکنون NETStandard نیز جهت رفع مشکلات کامپایل مجدد PCLها در حال توسعه است و پس از ارائهی آن، PCLها منسوخ شده درنظر گرفته میشوند. در این حالت با انتخاب target platform ایی به نام NETStandard (بجای مثلا انتخاب دات نت 4.5 و ویندوز فون 8)، اینبار دات نت 4.5 و ویندوز فون 8 و تمام پلتفرمهای دیگر، به صورت یکجا انتخاب میشوند و اگر پلتفرم جدیدی برای مثال از NETStandard نگارش 1.1 پشتیبانی کند، به این معنا است که کتابخانهی شما هم اکنون با آن سازگار است و دیگر نیازی به کامپایل مجدد آن نخواهد بود.
به علاوه هر برنامهای که بر اساس NETStandard تهیه شود، قابلیت اجرای بر روی NET Core. را نیز خواهد داشت. به عبارتی برنامههای NETStandard همان برنامههای مبتنی بر NET Core. هستند.
ASP.NET Core چیست؟
در زمان نگارش این مطلب، دو گزینهی برنامههای وب ASP.NET Core 1.0 و همچنین Windows Store apps (مبتنی بر NET Native Runtime.) قابلیت استفادهی از این پلتفرم جدید NET Core. دارند.
ASP.NET Core 1.0، که پیشتر با نام ASP.NET 5 معرفی شده بود، بازنویسی کامل ASP.NET است که با ایدهی کاملا ماژولار بودن، تهیه شدهاست و از طریق آن، قابلیت به روز رسانی منظم و توزیع آسان از طریق نیوگت، میسر خواهد شد. به علاوه در آن، بسیاری از الگوهای برنامه نویسی شیءگرا مانند تزریق وابستگیها، به صورت توکار و از ابتدا پشتیبانی میشوند.
ASP.NET Core 1.0 از WebForms ، VB ، WebPages و SignalR پشتیبانی نمیکند. البته در این بین عدم پشتیبانی از «وب فرمها» قطعی است؛ اما افزودن سه مورد دیگر یاد شده، جزو لیست کارهای پس از ارائهی نگارش 1 این فریم ورک قرار دارند و به زودی ارائه خواهند شد.
اکنون وضعیت ASP.NET MVC 5 و ASP.NET Web API 2 چگونه است؟
ASP.NET Core 1.0 مدل برنامه نویسی ASP.NET MVC و Web API را به صورت یکپارچه ارائه میدهد و دیگر خبری از ارائهی مجزای اینها نخواهد بود و دقیقا بر مبنای مفاهیم برنامه نویسی این دو بنا شدهاست. به صورت خلاصه MVC + Web API + Web Pages = Core MVC 1.0
پیشتر فضای نام System.Web.MVC مخصوص ASP.NET MVC بود و فضای نام مجزای دیگری به نام System.Web.Http مخصوص ASP.NET Web API. اما اکنون تنها یک فضای نام مشترک و یکپارچه به نام Microsoft.AspNet.Mvc هر دوی اینها را پوشش میدهد.
در این نگارش جدید وابستگی از system.web مبتنی بر IIS حذف شدهاست و با استفاده از هاست جدید چندسکویی به نام Kesterl، به سرعتی 5 برابر سرعت NodeJS دست یافتهاند.
آخرین تاریخ به روز رسانی ASP.NET MVC 5.x دوشنبه، 20 بهمن 1393 است (با ارائه نگارش 5.2.3 که آخرین نگارش رسمی و پایدار آن است) و آخرین تاریخ به روز رسانی ASP.NET Web API 2.x نیز همان روز است.
هرچند مایکروسافت عادت به اعلام رسمی پایان پشتیبانی از بسیاری از محصولات خود را ندارد اما تمام فناوریهای «قدیمی» خودش را بر روی CodePlex نگهداری میکند و تمام فناوریهای «جدید» را به GitHub منتقل کردهاست. بنابراین اگر در مورد فناوری خاصی به Codeplex رسیدید، یعنی «دیگر ادامهی رسمی نخواهد یافت» و حداکثر در حد رفع یک سری باگها و مشکلات گزارش شده باقی میمانند.
مثال 1: هم اکنون نگارش دوم ASP.NET Identity را بر روی Codeplex میتوانید مشاهده کنید. نگارش سوم آن به GitHub منتقل شدهاست که این نگارش صرفا با ASP.NET Core 1.0 سازگار است. در مورد ASP.NET MVC و Web API نیز چنین حالتی رخ دادهاست. نگارشهای 5 و 2 آنها بر روی Codeplex موجود هستند و نگارش ششم که به ASP.NET Core 1.0 تغییر نام یافت و ترکیبی است از MVC و Web API، در GitHub توسعه مییابد.
مثال 2: WCF به علت پیچیدگی بیش از حد و مدرن نبودن طراحی آن، رقابت را به ASP.NET Web API 2.x واگذار کرد و مدل برنامه نویسی ASP.NET Web API 2.x نیز هم اکنون جزئی از ASP.NET Core 1.0 است. بنابراین اگر قصد ایجاد پروژهی جدیدی را بر این مبنا دارید، بهتر است با APS.NET Core 1.0 کار را شروع کنید.
اما هنوز تعداد زیادی از کتابخانههای Full framework به NET Core. انتقال پیدا نکردهاند
برای نمونه هنوز EF Core 1.0 که پیشتر نام EF 7.x به آن داده شده بود، به مرحلهی نهایی تکمیل قابلیتهای آن نرسیدهاست. اما باید دانست که ASP.NET Core 1.0 صرفا بر فراز NET Core. قابل اجرا نیست؛ بلکه قابلیت اجرای بر فراز NET 4.6. و یا همان دات نت «بزرگ و کامل» را نیز دارد. بنابراین به سادگی قابلیت اجرای EF 6.x و یا NHibernate را نیز دارا است. تنها مزیتی را که در اینجا از دست خواهید، قابلیت چندسکویی بودن ASP.NET Core 1.0 است؛ زیرا EF 6.x با چنین دیدی طراحی نشدهاست.
همانطور که ملاحظه میکنید، ASP.NET Core 1.0 قابلیت اجرای بر روی هر دوی NET Core 1.0. و NET 4.6. را دارا است. اما یکی، چندسکویی است و دیگری صرفا مختص به ویندوز.
فناورهای منسوخ شدهی در NET Core.
یکسری از فناوریها و کتابخانهها احتمالا هیچگاه قابلیت انتقال به NET Core. را نخواهند یافت و یا حداقل باید تا چندنگارش بعدی آن صبر کنند. فناوریهای خاتمه یافتهی با NET Core. به شرح زیر هستند:
- Reflection: همانطور که عنوان شد، NET Core. بر فراز CoreCLR و همچنین NET Native runtime. اجرا میشود و تولید برنامههای native و static linking آنها مانند برنامههای ++C، نیاز به دانستن اجزایی دارد که به صورت پویا فراخوانی نمیشوند و بلافاصله و در زمان کامپایل، توسط کامپایلر قابل تشخیص هستند. همین محدودیت سبب شدهاست که استفادهی از Reflection در NET Core. به حداقل ممکن آن برسد. برای مثال در System.Object متد GetType آن تنها نام نوع را باز میگرداند و نه اطلاعات بیشتری را مانند GetMembers سابق.
- App Domains: هرچند CoreCLR از App Domains پشتیبانی میکند اما NET Native runtime. خیر. به همین جهت برای ایزوله سازی برنامهها توصیه شدهاست که از containerهایی مانند docker استفاده شود.
- Remoting: پیش از WCF جهت برقراری ارتباط بین برنامهها مطرح شده بود و هم اکنون در دات نت کامل هم آنچنان استفادهای از آن نمیشود.
- binary serialization: البته کتابخانههایی مانند JSON.NET و امثال آن، نگارش NET Core. هم دارند؛ اما چون binary serialization نیاز به اطلاعات reflection قابل توجهی دارد دیگر پشتیبانی نخواهد شد.
فناورهایی که به زودی به NET Core. منتقل میشوند
یکسری از فناوریها مانند XAML هنوز معادل NET Core. ندارند و لیست زیر قرار است از طرف مایکروسافت سورس باز شده و همچنین به NET Core. منتقل شود:
System.Data
System.DirectoryServices
System.Drawing
System.Transactions
System.Xml.Xsl and System.Xml.Schema
System.Net.Mail
System.IO.Ports
System.Workflow
System.Xaml
مراحل نصب ASP.NET Core 1.0
پیش از نصب نگارش 1.0 RTM باید به این نکته دقت داشت که نصاب آن، نگارشهای آزمایشی قبلی را حذف و یا بازنویسی نمیکند و همین مساله ممکن است سبب بروز تداخلهایی و یا حتی از کار افتادن VS.NET شما شود. بنابراین اگر نگارشهای RC یا بتا را پیشتر نصب کردهاید، به Add remove programs ویندوز مراجعه کرده و سه مورد ذیل را حتما حذف کنید (خیلی مهم):
- Preview Tooling (all versions)
- NET Core Runtime SDK (all versions).
- NET Core Runtime (all Versions).
پس از حذف بستههای قدیمی، برای نصب نگارش 1.0 RTM، ابتدا نیاز است Visual Studio 2015 Update 3 را نصب کنید و پس از آن با استفاده از NET Core for Visual Studio Official MSI Installer. کار نصب اجزای مورد نیاز آن انجام خواهد شد.
بررسی شماره نگارش 1.0 RTM
پس از نصب اجزای عنوان شده، خط فرمان را گشوده و دستور ذیل را صادر کنید:
C:\Users\Vahid>dotnet --version 1.0.0-preview2-003121
اگر یک پروژهی خالی ASP.NET Core Web Application را نیز شروع کنید (با طی مراحل زیر جهت ایجاد یک پروژهی جدید):
.NET Core -> ASP.NET Core Web Application (.NET Core) -> Select `Empty` Template
در اینجا فایل جدیدی را به نام global.json مشاهده میکنید که محتوایات آن شامل دقیقا همین شماره نگارش است؛ به همراه معرفی پوشههای اصلی پروژه:
{ "projects": [ "src", "test" ], "sdk": { "version": "1.0.0-preview2-003121" } }
نظرات اشتراکها
نگارش نهایی NET Framework 4.6. منتشر شد
ویندوز 10 به همراه نگارش 4.6 است:
استفادهی از Reflection در زیر ساختهای دات نت و ASP.NET Core، بسیار گستردهاست؛ به همین جهت هرگونه بهبود کارآیی در این زمینه، نه فقط بر روی خود فریمورک، بلکه تمام برنامههایی که از آن استفاده میکنند هم تاثیر گذار است. از این لحاظ دات نت 7 شاهد تغییرات گستردهای است تا حدی که کارآیی برنامههای مبتنی بر دات نت 7 ای که از Reflection استفاده میکنند، نسبت به نگارشهای قبلی دات نت، حداقل 2 برابر شدهاست و این برنامهها تنها کاری را که باید انجام دهند، صرفا تغییر target framework مورد استفادهی در آنها به نگارش جدید است. در این مطلب نحوهی رسیدن به این کارآیی بالاتر را بررسی خواهیم کرد.
تدارک یک آزمایش برای بررسی میزان افزایش کارآیی Reflection در دات نت 7
یک برنامهی کنسول جدید را ایجاد کرده و سپس کلاس Person را به صورت زیر به آن اضافه میکنیم:
همانطور که مشاهده میکنید، سازندهی این کلاس، internal است و همچنین دو متد private هم دارد که اگر بخواهیم از آن در جای دیگری استفاده کنیم، یکی از روشهای متداول جهت دسترسی به این امکانات خصوصی، استفاده از Reflection است.
به همین جهت ابتدا کتابخانهی BenchmarkDotNet را با TargetFramework دات نت 7 به صورت زیر به پروژه اضافه میکنیم:
در ادامه، یک کلاس آزمایش کارآیی برنامه را که با استفاده از Reflection، به امکانات خصوصی کلاس Person دسترسی پیدا میکند، مشاهده میکنید:
توضیحات:
- در اینجا نحوهی کار با متدهای خصوصی کلاس Person را توسط Reflection مشاهده میکنید. برای مثال در متد GetAge، به نحو متداولی این کار صورت گرفتهاست. در متد GetAgeCachedMethod، قسمت دریافت اطلاعات متد، کش شدهاست و برای نمونه در متد SetAgeCachedMethodCachedParams، هم کش شدن قسمت دریافت اطلاعات متد را مشاهده میکنید و هم کش شدن پارامتر ارسالی به آنرا.
- این آزمایش را با فراخوانی زیر و تنظیم target framework به دات نت 6 و سپس دات نت 7، به صورت جداگانهای اجرا میکنیم:
حاصل اجرای آن با target framework دات نت 6 به صورت زیر است:
و با target framework دات نت 7 به صورت زیر:
همانطور که مشاهده میکنید، در اکثر موارد، کارآیی Reflection در دات نت 7، حداقل 2 برابر نمونهی مشابه دات نت 6 است.
چه تغییری در دات نت 7 سبب بهبود قابل ملاحظهی کارآیی Reflection شدهاست؟
جزئیات تغییرات صورت گرفتهی در Reflection دات نت 7 را میتوانید در این pull request مشاهده کنید که در حقیقت از امکانات سطح پایین IL Emit استفاده کردهاند. در این مورد پیشتر تعدادی مطلب ذیل عنوان «آشنایی با Reflection.Emit» در این سایت منتشر شدهاند که میتوانید آنها را بررسی کنید.
در کل هرچند تغییرات جدید دات نت مانند ارائهی انواع و اقسام source generators، در تعدادی از موارد نیاز به Reflection را کمتر کردهاند و کارآیی بیشتری را ارائه دادهاند، اما Reflection هیچگاه منسوخ نخواهد شد و هرگونه بهبود کارآیی در این زمینه، بر روی کل فریمورک و برنامههای مشتق شدهی از آن، تاثیر قابل توجهی را خواهد گذاشت.
تدارک یک آزمایش برای بررسی میزان افزایش کارآیی Reflection در دات نت 7
یک برنامهی کنسول جدید را ایجاد کرده و سپس کلاس Person را به صورت زیر به آن اضافه میکنیم:
namespace NET7Reflection; public class Person { private int _age; internal Person(int age) => _age = age; private int GetAge() => _age; private void SetAge(int age) => _age = age; }
به همین جهت ابتدا کتابخانهی BenchmarkDotNet را با TargetFramework دات نت 7 به صورت زیر به پروژه اضافه میکنیم:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net7.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="BenchmarkDotNet" Version="0.13.4" /> </ItemGroup> </Project>
using System.Reflection; using BenchmarkDotNet.Attributes; namespace NET7Reflection; [MemoryDiagnoser(false)] public class Benchmarks { private readonly object?[] _ageParams = { 30 }; private readonly ConstructorInfo _ctor = typeof(Person).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(int) })!; private readonly MethodInfo _getAgeMethod = typeof(Person).GetMethod("GetAge", BindingFlags.NonPublic | BindingFlags.Instance)!; private readonly Person _person = new(10); private readonly MethodInfo _setAgeMethod = typeof(Person).GetMethod("SetAge", BindingFlags.NonPublic | BindingFlags.Instance)!; [Benchmark] public int GetAge() => (int)typeof(Person).GetMethod("GetAge", BindingFlags.NonPublic | BindingFlags.Instance)! .Invoke(_person, Array.Empty<object?>())!; [Benchmark] public int GetAgeCachedMethod() => (int)_getAgeMethod.Invoke(_person, Array.Empty<object?>())!; [Benchmark] public void SetAge() => typeof(Person).GetMethod("SetAge", BindingFlags.NonPublic | BindingFlags.Instance)! .Invoke(_person, new object?[] { 30 }); [Benchmark] public void SetAgeCachedMethod() => _setAgeMethod.Invoke(_person, new object?[] { 30 }); [Benchmark] public void SetAgeCachedMethodCachedParams() => _setAgeMethod.Invoke(_person, _ageParams); [Benchmark] public Person Ctor() => (Person)typeof(Person).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(int) })! .Invoke(_person, new object?[] { 30 })!; [Benchmark] public Person CtorCachedCtorInfo() => (Person)_ctor.Invoke(_person, new object?[] { 30 })!; [Benchmark] public Person CtorCachedCtorInfoCachedParams() => (Person)_ctor.Invoke(_person, _ageParams)!; }
- در اینجا نحوهی کار با متدهای خصوصی کلاس Person را توسط Reflection مشاهده میکنید. برای مثال در متد GetAge، به نحو متداولی این کار صورت گرفتهاست. در متد GetAgeCachedMethod، قسمت دریافت اطلاعات متد، کش شدهاست و برای نمونه در متد SetAgeCachedMethodCachedParams، هم کش شدن قسمت دریافت اطلاعات متد را مشاهده میکنید و هم کش شدن پارامتر ارسالی به آنرا.
- این آزمایش را با فراخوانی زیر و تنظیم target framework به دات نت 6 و سپس دات نت 7، به صورت جداگانهای اجرا میکنیم:
using BenchmarkDotNet.Running; using NET7Reflection; BenchmarkRunner.Run<Benchmarks>();
و با target framework دات نت 7 به صورت زیر:
همانطور که مشاهده میکنید، در اکثر موارد، کارآیی Reflection در دات نت 7، حداقل 2 برابر نمونهی مشابه دات نت 6 است.
چه تغییری در دات نت 7 سبب بهبود قابل ملاحظهی کارآیی Reflection شدهاست؟
جزئیات تغییرات صورت گرفتهی در Reflection دات نت 7 را میتوانید در این pull request مشاهده کنید که در حقیقت از امکانات سطح پایین IL Emit استفاده کردهاند. در این مورد پیشتر تعدادی مطلب ذیل عنوان «آشنایی با Reflection.Emit» در این سایت منتشر شدهاند که میتوانید آنها را بررسی کنید.
در کل هرچند تغییرات جدید دات نت مانند ارائهی انواع و اقسام source generators، در تعدادی از موارد نیاز به Reflection را کمتر کردهاند و کارآیی بیشتری را ارائه دادهاند، اما Reflection هیچگاه منسوخ نخواهد شد و هرگونه بهبود کارآیی در این زمینه، بر روی کل فریمورک و برنامههای مشتق شدهی از آن، تاثیر قابل توجهی را خواهد گذاشت.
SIMD مخفف «Single Instruction, Multiple Data» است و متشکل است از تعدادی instruction پردازندهها که بجای مقادیر عددی، بر روی بردارها کار میکنند. به این ترتیب امکان کار موازی بر روی مقادیر عددی، در سطح CPU میسر میشود. برای نمونه به تصویر ذیل دقت کنید:
در اینجا قرار است تک تک عناصر آرایهای از اعداد، با عدد 6 جمع شوند. روش متداول آن به این صورت است که حلقهای تشکیل شده و سپس تک تک عناصر این آرایه دریافت و با عدد 6 جمع میشوند. اما در حالت استفادهی از SIMD، هربار گروهی از عناصر این آرایه به صورت یک بردار درنظر گرفته میشوند (Multiple Data) و سپس با برداری حاوی مقدار 6 جمع میشوند (Single Instruction). اینبار این عملیات به صورت موازی، بر روی گروهی از اعداد انجام میشود و به همین دلیل نسبت به حالت کار بر روی یک المان از آرایه در هر مرحله، سرعت بیشتری دارد.
تفاوت چندریسمانی با SIMD چیست؟
شاید عنوان کنید که با وجود امکانات چندریسمانی چه نیازی به SIMD است؟ در حالت پردازشهای چند ریسمانی، یک یا چند کار بر روی چندین هستهی CPU به صورت موازی پردازش میشوند، اما SIMD امکان پردازش موازی را در یک هستهی CPU میسر میکند.
آیا CPU من از SIMD پشتیبانی میکند؟
SIMD instruction sets شامل افزونههای ذیل است:
اگر CPU شما حداقل یکی از این قابلیتها را داشته باشد، امکان استفادهی از SIMD را دارید. برای مشخص سازی آن نیز میتوانید از برنامهی معروف CPU-Z استفاده کنید:
در این برنامه، در برگهی CPU آن به قسمت instructions آن دقت کنید و موارد لیست شدهی در آن را با افزونههای فوق مقایسه نمائید.
پشتیبانی از SIMD در دات نت
با ارائهی دات نت 4.6 و RyuJIT جدید آن، امکان کار با دستورات SIMD در فضای نام System.Numerics.Vectors پیش بینی شدهاست. برای کار با آن باید بستهی نیوگت زیر را نصب کنید:
در ابتدای کار باید بررسی کنید که آیا سخت افزار شما از SIMD پشتیبانی میکند یا خیر. خاصیت Vector.IsHardwareAccelerated بیانگر این موضوع است. اما ... این خاصیت در حال دیباگ ممکن است مساوی false باشد. برای استفادهی از SIMD ، طی این مراحل ضروری است:
الف) نصب دات نت 4.6.x (دریافت دات نت 4.6.1 مخصوص یکپارچه شدن با ویژوال استودیو)
ب) به خواص پروژهی جاری مراجعه کرده و platform target را بر روی x64 قرار دهید. باید دقت داشت که RyuJIT جدید، برای سیستمهای 64 بیتی طراحی شدهاست.
ج) RyuJIT، در حالت release و انتخاب گزینهی optimize code (در همان برگهی خواص پروژه) است که کدهای ویژهی SIMD را تولید میکند.
د) نصب بستهی نیوگت System.Numerics.Vectors
در کل اگر برنامه را داخل دیباگر VS.NET اجرا کنید، مقدار Vector.IsHardwareAccelerated مساوی false خواهد بود. به همین جهت برنامه را در حالت release و 64 بیتی کامپایل کرده و خارج از محیط VS.NET اجرا کنید.
بررسی فضای نام جدید System.Numerics.Vectors
پشتیبانی از SIMD در دات نت به این معنا نیست که هر نوع کدی توسط RyuJIT به صورت خودکار تبدیل به SIMD instruction sets خواهد شد. برای این منظور نیاز است از نوعهای دادهای خاصی به همراه متدهای مرتبط با آنها استفاده کرد.
سری اول این نوعهای جدید برداری، به شرح زیر هستند:
کلاسهای وکتور 2، 3 و 4، بردارهایی از نوع float را با اندازههایی ثابت تعریف میکنند و بر روی 128bit SIMD registers کار میکنند. بر روی این کلاسها، با توجه به operators overloading صورت گرفته، امکان جمع، منها، ضرب و تقسیم نیز وجود دارد و یا میتوان از متدهای متناظر موجود در کلاسهای آنها استفاده کرد. نمونهای از این عملیات را در مثالهای ذیل مشاهده میکنید:
در مثال آخر مطرح شده، روشی کپی و تبدیل یک بردار، به یک آرایهی هم نوع آن، ارائه شدهاست.
علاوه بر اعمال متداول ریاضی، هر کدام از کلاسهای Vector دارای متدهای اضافی ویژهای مانند محاسبهی حداقل، حداکثر، جذر و غیره نیز میباشند:
برای مثال متد Max در اینجا به MAXPS instruction مخصوص پردازشگر ترجمه میشود.
سری دوم بردارهای قابل تعریف، از نوع <Vector<T هستند. برای مثال CPUهایی که از SSE2 پشتیبانی میکنند، قابلیت کار با نوعهای دادهای زیر را نیز دارا هستند:
برای نمونه همان مثال ابتدای بحث را در نظر بگیرید. نسخهی متداول انجام افزودن مقداری به تک تک اعضای یک آرایه به صورت زیر است:
بازنویسی این متد برای کار با SIMD به صورت ذیل خواهد بود:
در اینجا یک Vector از نوع int تعریف شده و سپس بجای تشکیل یک حلقه، فقط کافی است بردار دیگری را حاوی عدد مشخص شده، به آن اضافه کنیم. در پایان برای تبدیل این بردار به آرایهای از نوع int (در صورت نیاز) میتوان از متد Copy استفاده کرد.
در مثال ذیل، نحوهی انتخاب Multiple data (گروهی از اعداد، بجای تک عدد) و سپس اعمال یک تک instruction را ملاحظه میکنید:
در مثال فوق قصد داریم جذر تک تک عناصر آرایهای را محاسبه کرده و سپس در آرایهی دومی ثبت کنیم. بجای روش متداول مراجعهی به تک تک عناصر آرایهی ورودی، اینبار با استفاده از کلاس بردار، به اندازهی طول بردار float، اطلاعات را در vectorIn ذخیره کرده و سپس به صورت یکجا به تک متد SquareRoot ارسال میکنیم. این متد در سمت CPU به معادل SQRTPS instruction ترجمه میشود و تنها یک instruction است.
یک مثال تکمیلی
در اینجا قرار است تک تک عناصر آرایهای از اعداد، با عدد 6 جمع شوند. روش متداول آن به این صورت است که حلقهای تشکیل شده و سپس تک تک عناصر این آرایه دریافت و با عدد 6 جمع میشوند. اما در حالت استفادهی از SIMD، هربار گروهی از عناصر این آرایه به صورت یک بردار درنظر گرفته میشوند (Multiple Data) و سپس با برداری حاوی مقدار 6 جمع میشوند (Single Instruction). اینبار این عملیات به صورت موازی، بر روی گروهی از اعداد انجام میشود و به همین دلیل نسبت به حالت کار بر روی یک المان از آرایه در هر مرحله، سرعت بیشتری دارد.
تفاوت چندریسمانی با SIMD چیست؟
شاید عنوان کنید که با وجود امکانات چندریسمانی چه نیازی به SIMD است؟ در حالت پردازشهای چند ریسمانی، یک یا چند کار بر روی چندین هستهی CPU به صورت موازی پردازش میشوند، اما SIMD امکان پردازش موازی را در یک هستهی CPU میسر میکند.
آیا CPU من از SIMD پشتیبانی میکند؟
SIMD instruction sets شامل افزونههای ذیل است:
• MMX - MultiMedia eXtensions • SSE - Streaming SIMD Extensions • SSE2 - Streaming SIMD Extensions 2 • SSE3 - Streaming SIMD Extensions 3 • SSSE3 - Supplemental Streaming SIMD Extensions 3 • SSE4.1 - Streaming SIMD Extensions 4.1 • SSE4.2 - Streaming SIMD Extensions 4.2 • AES-NI - Advanced Encryption Standard New Instructions • AVX - Advanced Vector eXtensions
در این برنامه، در برگهی CPU آن به قسمت instructions آن دقت کنید و موارد لیست شدهی در آن را با افزونههای فوق مقایسه نمائید.
پشتیبانی از SIMD در دات نت
با ارائهی دات نت 4.6 و RyuJIT جدید آن، امکان کار با دستورات SIMD در فضای نام System.Numerics.Vectors پیش بینی شدهاست. برای کار با آن باید بستهی نیوگت زیر را نصب کنید:
PM> Install-Package System.Numerics.Vectors
الف) نصب دات نت 4.6.x (دریافت دات نت 4.6.1 مخصوص یکپارچه شدن با ویژوال استودیو)
ب) به خواص پروژهی جاری مراجعه کرده و platform target را بر روی x64 قرار دهید. باید دقت داشت که RyuJIT جدید، برای سیستمهای 64 بیتی طراحی شدهاست.
ج) RyuJIT، در حالت release و انتخاب گزینهی optimize code (در همان برگهی خواص پروژه) است که کدهای ویژهی SIMD را تولید میکند.
د) نصب بستهی نیوگت System.Numerics.Vectors
در کل اگر برنامه را داخل دیباگر VS.NET اجرا کنید، مقدار Vector.IsHardwareAccelerated مساوی false خواهد بود. به همین جهت برنامه را در حالت release و 64 بیتی کامپایل کرده و خارج از محیط VS.NET اجرا کنید.
بررسی فضای نام جدید System.Numerics.Vectors
پشتیبانی از SIMD در دات نت به این معنا نیست که هر نوع کدی توسط RyuJIT به صورت خودکار تبدیل به SIMD instruction sets خواهد شد. برای این منظور نیاز است از نوعهای دادهای خاصی به همراه متدهای مرتبط با آنها استفاده کرد.
سری اول این نوعهای جدید برداری، به شرح زیر هستند:
var vector01 = new Vector2(x: 5F, y: 15F); var vector11 = new Vector3(x: 5F, y: 15F, z: 25F); var vector12 = new Vector3(x: 3F, y: 5F, z: 8F); var vector13 = new Vector4(x: 3F, y: 5F, z: 8F, w:1F);
var vector3 = vector11 - vector12; //استفاده از سربارگذاری عملگرها var vector4 = Vector3.Subtract(vector12, vector11);//ویا استفاده از متدهای متناظر vector3 = vector11 * vector12; vector4 = Vector3.Multiply(vector11, vector12); vector3 = vector11 / vector12; vector4 = Vector3.Divide(vector11, vector12); vector3 = vector11 + vector12; vector4 = Vector3.Add(vector11, vector12); var areEqual = (vector11 == vector12); var areNotEqual = (vector11 != vector12); var array = new float[3]; vector11.CopyTo(array);
علاوه بر اعمال متداول ریاضی، هر کدام از کلاسهای Vector دارای متدهای اضافی ویژهای مانند محاسبهی حداقل، حداکثر، جذر و غیره نیز میباشند:
vector3 = Vector3.Max(vector11, vector12); vector3 = Vector3.Min(vector11, vector12); vector3 = Vector3.SquareRoot(vector11); vector3 = Vector3.Abs(vector11); var dotProduct = Vector3.Dot(vector11, vector12);
سری دوم بردارهای قابل تعریف، از نوع <Vector<T هستند. برای مثال CPUهایی که از SSE2 پشتیبانی میکنند، قابلیت کار با نوعهای دادهای زیر را نیز دارا هستند:
Vector<double>.Length: 2 Vector<int>.Length: 4 Vector<long>.Length: 2 Vector<float>.Length: 4
private static int[] simpleIncrement(int[] values, int inc) { var results = new int[values.Length]; for (var i = 0; i < results.Length; i++) { results[i] = values[i] + inc; } return results; }
private static int[] simdIncrement(int[] values, int inc) { var vector = new Vector<int>(values); var vectorAddResults = vector + new Vector<int>(inc); var results = new int[values.Length]; vectorAddResults.CopyTo(results); return results; }
در مثال ذیل، نحوهی انتخاب Multiple data (گروهی از اعداد، بجای تک عدد) و سپس اعمال یک تک instruction را ملاحظه میکنید:
var valuesIn = new float[] { 4f, 16f, 36f, 64f, 9f, 81f, 49f, 25f, 100f, 121f, 144f, 16f, 36f, 4f, 9f, 81f }; var valuesOut = new float[valuesIn.Length]; for (var i = 0; i < valuesIn.Length; i += Vector<float>.Count) { var vectorIn = new Vector<float>(valuesIn, i); var vectorOut = Vector.SquareRoot(vectorIn); vectorOut.CopyTo(valuesOut, i); }
یک مثال تکمیلی
نظرات اشتراکها
نگارش نهایی NET Framework 4.6. منتشر شد
نصاب آفلاین نگارش 4.6 را از اینجا میتوانید دریافت کنید.
مطالب
Span در C# 7.2
C# 7.2 به همراه تعداد کوچکی از بهبودهای کامپایلر است و با Visual Studio 2017 نگارش 15.5 ارائه شده و روش فعالسازی آن با نگارش 7.1 آن یکی است (انتخاب گزینهی «C# latest minor version (latest)» در تنظیمات پیشرفتهی Build خواص پروژه). همچنین اگر از VSCode استفاده میکنید، نگارش 1.14 افزونهی #C آن، پشتیبانی کاملی را از C# 7.2 به همراه دارد؛ در اینجا، افزودن خاصیت <LangVersion>latest</LangVersion> به فایل csproj برنامه برای استفادهی از آخرین نگارش کامپایلر نصب شده، کفایت میکند. البته باید دقت داشت کامپایلر C# 7.2 به همراه NET Core SDK 2.1.2. ارائه شدهاست. بنابراین تنها نصب آخرین نگارش افزونهی #C مخصوص VSCode برای کامپایل آن کافی نیست و باید حداقل SDK یاد شده (یا نگارش جدیدتر آن) را هم نصب کنید.
نوعهای جدید <Span<T و <ReadOnlySpan<T در C# 7.2
نوعهای جدید <Span<T و <ReadOnlySpan<T جهت ارائهی ناحیههای اختیاری پیوستهای از حافظه، شبیه به آرایهها تدارک دیده شدهاند و هدف استفادهی از آنها، تولید برنامههای سمت سرور با کارآیی بالا است.
برای کار با این نوعها، هم نیاز به کامپایلر C# 7.2 است و هم نصب بستهی نیوگت System.Memory:
این بسته از .NETStandard 1.0. به بعد را پشتیبانی میکند؛ یعنی با +NET 4.5+ ،Mono 4.6. و +NET Core 1.0. سازگار است.
Spanها و امکان دسترسی به انواع حافظه
Spanها میتوانند به حافظهی مدیریت شده، حافظهی بومی (native) و حافظهی اختصاص داده شدهی در Stack اشاره کنند. به عبارتی Spanها یک لایه انتزاعی، برفراز تمام انواع و اقسام حافظههایی هستند که میتوانند در اختیار توسعه دهندگان NET. باشند.
- البته اکثر توسعه دهندگان دات نت از حافظهی مدیریت شده استفاده میکنند. برای مثال Stack memory تنها از طریق کدهای unsafe و واژهی کلیدی stackalloc قابل تخصیص است. این نوع حافظه بسیار سریع است و همچنین بسیار کوچک؛ کمتر از یک مگابایت که به خوبی در CPU cache جا میشود. اما اگر در این بین حجم حافظهی تخصیصی بیشتر از یک مگابایت شود، بلافاصله استثنای StackOverflowException غیرقابل مدیریتی را به همراه خاتمهی فوری برنامه به همراه خواهد داشت. برای نمونه از این نوع حافظه در جهت مدیریت رخدادهای داخلی corefx زیاد استفاده میشود.
- حافظهی مدیریت شده، همان حافظهای است که توسط واژهی کلیدی new در برنامه، جهت ایجاد اشیاء، تخصیص داده میشود و طول عمر آن تحت مدیریت GC است.
- حافظهی مدیریت نشده یا بومی از دید GC مخفی است و توسط متدهایی مانند Marshal.AllocHGlobal و Marshal.AllocCoTaskMem در اختیار برنامه قرار میگیرند. این حافظه باید به صورت صریحی توسط توسعه دهنده به کمک متدهایی مانند Marshal.FreeHGlobal و Marshal.FreeCoTaskMem آزاد شود. وب سرور Kestrel مخصوص ASP.NET Core، از این روش جهت کار با آرایههای حجیم، جهت کاهش بار GC استفاده میکند.
مزیت کار با Spanها این است که دسترسی امن و type safeایی را به انواع حافظههای مهیا، جهت توسعه دهندگانی که عموما کدهای unsafe ایی را نمینویسند و با اشارهگرها به صورت مستقیم کار نمیکنند، میسر میکند. برای مثال تا پیش از معرفی Spanها، برای دسترسی به 1000 عنصر یک آرایهی 10 هزار عنصری و ارسال آن به یک متد، نیاز بود تا ابتدا یک کپی از این 1000 عنصر را تهیه کرد. این عملیات از لحاظ میزان مصرف حافظه و همچنین زمان انجام آن، بسیار هزینهبر است. با استفاده از <Span<T میتوان یک دید مجازی از آن آرایه را بدون اختصاص آرایهای و یا آرایههایی جدید، ارائه کرد.
مثالی از کاربرد Spanها جهت کاهش تعداد بار تخصیصهای حافظه
برای نمونه، متد IsValidName زیر، بررسی میکند که طول رشتهی دریافتی حداقل 2 باشد و حتما با یک حرف شروع شده باشد:
در این حالت یک نمونه مثال از استفادهی آن میتواند به صورت زیر باشد:
در اینجا زمانیکه از متد Substring استفاده میشود، در حقیقت تخصیص حافظهی دومی جهت تولید firstName رخ میدهد.
همچنین اگر این اطلاعات را از طریق شبکه دریافت کرده باشیم، ممکن است به صورت آرایهای از حروف دریافت شوند:
که به صورت مستقیم در متد IsValidName قابل استفاده نیست و خطای عدم امکان تبدیل []char به string، از طرف کامپایلر صادر میشود:
در این حالت برای استفادهی از این آرایه، نیاز است یک تخصیص حافظهی دیگر نیز صورت گیرد:
اکنون در C# 7.2، بازنویسی این متد توسط ReadOnlySpan، به صورت ذیل است:
که این مزایا را به همراه دارد:
کار با API مربوط به Spanها به همراه تخصیص حافظهی جدیدی نیست. برای نمونه در اینجا متد Slice این API، سبب تخصیص حافظهی جدیدی نمیشود (برخلاف متد Substring) و فقط به قسمتی از حافظهی موجود اشاره میکند (بدون نیاز به کار مستقیم با اشارهگرها و کدهای unsafe).
و یا اینبار امکان استفادهی از آرایهای از کاراکترها، بدون نیاز به تخصیص حافظهای جدید، برای بررسی اعتبار مقادیر دریافتی میسر است:
برای نمونه از یک چنین APIایی در پشت صحنهی کتابخانههایی مانند SignalR و یا Roslyn، برای بالا بردن کارآیی برنامه، با کاهش تعداد بار تخصیصهای حافظهی مورد نیاز، بسیار استفاده شدهاست. برای نمونه در NET Core 2.1.، حجم رشتههای تخصیص داده شدهی در فریم ورکهای وابسته، به این ترتیب به شدت کاهش یافتهاست.
مثالهایی از کار با API نوع Span
امکان ایجاد یک Span از یک array
پس از آن کار با این span همانند کار با آرایههای معمولی است؛ با این تفاوت که این span تنها یک دید مجازی از قسمتی از این آرایه را ارائه میدهد؛ بدون سربار تخصیص حافظهی اضافی و کپی اطلاعات:
در اینجا slicedBytes یک دید مجازی از ایندکس 5 تا 7 آرایهی arr را ارائه میدهد. کار کردن با آن نیز همانند آرایهها، توسط ایندکسها میسر است.
همچنین تغییرات بر روی Span (غیر read only) بر روی آرایهی اصلی نیز تاثیر گذار است. برای مثال در اینجا با تغییر bytes[2]، مقدار arr[2] نیز تغییر میکند.
و یا روش دیگر ایجاد Span استفاده از متد AsSpan است:
همین عملیات را توسط new Span نیز میتوان به صورت سادهتری ارائه داد:
محدودیتهای کار با Spanها
- Span تنها یک نوع stack-only است.
- Spanها در بین تردها به اشتراک گذاشته نمیشوند. هر استک در یک زمان تنها توسط یک ترد قابل دسترسی است. بنابراین Spanها thread-safe هستند.
- طول عمر Spanها کوتاه است و قابلیت قرارگیری بر روی heap با طول عمر بیشتر را ندارند؛ یعنی:
فیلدهای یک کلاس در heap ذخیره میشوند. بنابراین محل ذخیره سازی spanها نیستند.
معرفی نوع <Memory<T
با توجه به محدودیتهای Span و خصوصا اینکه به عنوان پارامتر متدهای async قابل استفاده نیست (چون بر روی stack ذخیره میشوند)، نوع دیگری به نام <Memory<T نیز به همراه C# 7.2 ارائه شدهاست. البته این نوع هنوز به بستهی نیوگت فوق اضافه نشدهاست و به همراه ارائه نهایی NET Core 2.1. ارائه خواهد شد.
این نوع، محدودیت <Span<T را نداشته و قابلیت ذخیره سازی بر روی heap را دارا است.
در اینجا نیز میتوان از یک آرایه، یک <Memory<T را ایجاد و سپس یک <Span<T را از آن دریافت و با Sliceهای آن کار کرد.
نوعهای جدید <Span<T و <ReadOnlySpan<T در C# 7.2
نوعهای جدید <Span<T و <ReadOnlySpan<T جهت ارائهی ناحیههای اختیاری پیوستهای از حافظه، شبیه به آرایهها تدارک دیده شدهاند و هدف استفادهی از آنها، تولید برنامههای سمت سرور با کارآیی بالا است.
برای کار با این نوعها، هم نیاز به کامپایلر C# 7.2 است و هم نصب بستهی نیوگت System.Memory:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.0</TargetFramework> <LangVersion>latest</LangVersion> </PropertyGroup> <ItemGroup> <PackageReference Include="System.Memory" Version="4.4.0-preview1-25305-02" /> </ItemGroup> </Project>
Spanها و امکان دسترسی به انواع حافظه
Spanها میتوانند به حافظهی مدیریت شده، حافظهی بومی (native) و حافظهی اختصاص داده شدهی در Stack اشاره کنند. به عبارتی Spanها یک لایه انتزاعی، برفراز تمام انواع و اقسام حافظههایی هستند که میتوانند در اختیار توسعه دهندگان NET. باشند.
- البته اکثر توسعه دهندگان دات نت از حافظهی مدیریت شده استفاده میکنند. برای مثال Stack memory تنها از طریق کدهای unsafe و واژهی کلیدی stackalloc قابل تخصیص است. این نوع حافظه بسیار سریع است و همچنین بسیار کوچک؛ کمتر از یک مگابایت که به خوبی در CPU cache جا میشود. اما اگر در این بین حجم حافظهی تخصیصی بیشتر از یک مگابایت شود، بلافاصله استثنای StackOverflowException غیرقابل مدیریتی را به همراه خاتمهی فوری برنامه به همراه خواهد داشت. برای نمونه از این نوع حافظه در جهت مدیریت رخدادهای داخلی corefx زیاد استفاده میشود.
- حافظهی مدیریت شده، همان حافظهای است که توسط واژهی کلیدی new در برنامه، جهت ایجاد اشیاء، تخصیص داده میشود و طول عمر آن تحت مدیریت GC است.
- حافظهی مدیریت نشده یا بومی از دید GC مخفی است و توسط متدهایی مانند Marshal.AllocHGlobal و Marshal.AllocCoTaskMem در اختیار برنامه قرار میگیرند. این حافظه باید به صورت صریحی توسط توسعه دهنده به کمک متدهایی مانند Marshal.FreeHGlobal و Marshal.FreeCoTaskMem آزاد شود. وب سرور Kestrel مخصوص ASP.NET Core، از این روش جهت کار با آرایههای حجیم، جهت کاهش بار GC استفاده میکند.
مزیت کار با Spanها این است که دسترسی امن و type safeایی را به انواع حافظههای مهیا، جهت توسعه دهندگانی که عموما کدهای unsafe ایی را نمینویسند و با اشارهگرها به صورت مستقیم کار نمیکنند، میسر میکند. برای مثال تا پیش از معرفی Spanها، برای دسترسی به 1000 عنصر یک آرایهی 10 هزار عنصری و ارسال آن به یک متد، نیاز بود تا ابتدا یک کپی از این 1000 عنصر را تهیه کرد. این عملیات از لحاظ میزان مصرف حافظه و همچنین زمان انجام آن، بسیار هزینهبر است. با استفاده از <Span<T میتوان یک دید مجازی از آن آرایه را بدون اختصاص آرایهای و یا آرایههایی جدید، ارائه کرد.
مثالی از کاربرد Spanها جهت کاهش تعداد بار تخصیصهای حافظه
برای نمونه، متد IsValidName زیر، بررسی میکند که طول رشتهی دریافتی حداقل 2 باشد و حتما با یک حرف شروع شده باشد:
static class NameValidatorUsingString { public static bool IsValidName(string name) { if (name.Length < 2) return false; if (char.IsLetter(name[0])) return true; return false; } }
string fullName = "User 1"; string firstName = fullName.Substring(0, 4); NameValidatorUsingString.IsValidName(firstName);
همچنین اگر این اطلاعات را از طریق شبکه دریافت کرده باشیم، ممکن است به صورت آرایهای از حروف دریافت شوند:
char[] anotherFullName = { 'A', 'B' };
NameValidatorUsingString.IsValidName(anotherFullName);
NameValidatorUsingString.IsValidName(new string(anotherFullName));
اکنون در C# 7.2، بازنویسی این متد توسط ReadOnlySpan، به صورت ذیل است:
static class NameValidatorUsingSpan { public static bool IsValidName(ReadOnlySpan<char> name) { if (name.Length < 2) return false; if (char.IsLetter(name[0])) return true; return false; } }
ReadOnlySpan<char> fullName = "User 1".AsSpan(); ReadOnlySpan<char> firstName = fullName.Slice(0, 4); NameValidatorUsingSpan.IsValidName(firstName);
و یا اینبار امکان استفادهی از آرایهای از کاراکترها، بدون نیاز به تخصیص حافظهای جدید، برای بررسی اعتبار مقادیر دریافتی میسر است:
char[] anotherFullName = { 'A', 'B' }; NameValidatorUsingSpan.IsValidName(anotherFullName);
برای نمونه از یک چنین APIایی در پشت صحنهی کتابخانههایی مانند SignalR و یا Roslyn، برای بالا بردن کارآیی برنامه، با کاهش تعداد بار تخصیصهای حافظهی مورد نیاز، بسیار استفاده شدهاست. برای نمونه در NET Core 2.1.، حجم رشتههای تخصیص داده شدهی در فریم ورکهای وابسته، به این ترتیب به شدت کاهش یافتهاست.
مثالهایی از کار با API نوع Span
امکان ایجاد یک Span از یک array
var arr = new byte[10]; Span<byte> bytes = arr; // Implicit cast from T[] to Span<T>
Span<byte> slicedBytes = bytes.Slice(start: 5, length: 2); slicedBytes[0] = 42; slicedBytes[1] = 43; slicedBytes[2] = 44; // Throws IndexOutOfRangeException bytes[2] = 45; // OK
همچنین تغییرات بر روی Span (غیر read only) بر روی آرایهی اصلی نیز تاثیر گذار است. برای مثال در اینجا با تغییر bytes[2]، مقدار arr[2] نیز تغییر میکند.
و یا روش دیگر ایجاد Span استفاده از متد AsSpan است:
var array = new byte[100]; Span<byte> interiorRef1 = array.AsSpan().Slice(start: 20);
Span<byte> interiorRef2 = new Span<byte>(array: array, start: 20, length: array.Length - 20);
محدودیتهای کار با Spanها
- Span تنها یک نوع stack-only است.
- Spanها در بین تردها به اشتراک گذاشته نمیشوند. هر استک در یک زمان تنها توسط یک ترد قابل دسترسی است. بنابراین Spanها thread-safe هستند.
- طول عمر Spanها کوتاه است و قابلیت قرارگیری بر روی heap با طول عمر بیشتر را ندارند؛ یعنی:
- به صورت فیلد در یک نوع non-stackonly قابل تعریف نیستند:
class Impossible { Span<byte> field; }
- به عنوان پارامترهای متدهای async قابل استفاده نیستند. چون در این بین در پشت صحنه یک AsyncMethodBuilder تشکیل میشود که در قسمتی از آن، پارامترها بر روی heap قرار میگیرند.
- هرجائیکه عملیات boxing صورت گیرد، نتیجهی عملیات بر روی heap قرار میگیرد. بنابراین در یک چنین مواردی نمیتوان از Spanها استفاده کرد. برای مثال تعریف Func<T> valueProvider و سپس فراخوانی ()valueProvider.Invoke به همراه یک boxing است. بنابراین نمیتوان از spanها به عنوان نوع آرگومان جنریک استفاده کرد. این مورد هرچند کامپایل میشود، اما در زمان اجرا سبب خاتمهی برنامه خواهد شد و یا نمونهی دیگر، عدم امکان دسترسی به آنها توسط reflection invoke APIs است که سبب boxing میشود.
معرفی نوع <Memory<T
با توجه به محدودیتهای Span و خصوصا اینکه به عنوان پارامتر متدهای async قابل استفاده نیست (چون بر روی stack ذخیره میشوند)، نوع دیگری به نام <Memory<T نیز به همراه C# 7.2 ارائه شدهاست. البته این نوع هنوز به بستهی نیوگت فوق اضافه نشدهاست و به همراه ارائه نهایی NET Core 2.1. ارائه خواهد شد.
این نوع، محدودیت <Span<T را نداشته و قابلیت ذخیره سازی بر روی heap را دارا است.
static async Task<int> ChecksumReadAsync(Memory<byte> buffer, Stream stream) { int bytesRead = await stream.ReadAsync(buffer); return Checksum(buffer.Span.Slice(0, bytesRead)); // Or buffer.Slice(0, bytesRead).Span }
اشتراکها