اشتراکها
اشتراکها
شروع کار با Regular Expression
مطالب
Generators در ES 6
Generators در حقیقت نوعی Iterator هستند. آنها نوع خاصی از توابع هستند که قابلیت تعلیق و از سرگیری مجدد را دارند. برای رسیدن به این هدف، اینبار تعریف function به صورت *function خواهد بود و در آن برای بازگشت مقادیر، از واژهی کلیدی yield استفاده میشود.
یک نمونه مثال ابتدایی از Generators را در کدهای زیر مشاهده میکنید:
این متد خاص با یک ستاره پس از نام function مشخص شدهاست و همچنین برای بازگشت مقادیر از واژهی کلیدی yield استفاده میکند. روش فراخوانی دستی آن نیز به صورت زیر است:
همانطور که مشاهده میکنید، همانند Iterators، هر بار که متد next آنها فراخوانی میشود، شیءایی را با خواص value و done بازگشت میدهند. هر زمانیکه done مساوی true شد، یعنی کار آن به پایان رسیدهاست.
و یا میتوان بجای فراخوانی دستی متد next، از حلقهی جدید for of نیز برای کار با آنها استفاده کرد:
امکان ترکیب Generators نیز وجود دارد:
در اینجا یک متد Generator به نام random1_20، به تعداد نامتناهی اعداد اتفاقی بین 1 تا 20 را بازگشت میدهد و در این بین، yield آن خود نیز یک Generator دیگر است.
فقط در این حالت بجای yield معمولی از *yield استفاده میشود. از *yield برای کار با هر نوع Iterator ایی میتوان استفاده کرد:
در این مثال از yield معمولی برای بازگشت اعداد و از *yiled برای کار با انواع و اقسام Iterators از آرایهها گرفته تا spread operator، استفاده شدهاست.
کاهش مصرف حافظهی برنامه با استفاده از Generators
در مثال زیر، قرار است لیستی از rows بازگشت داده شود. در اینجا یک آرایه تشکیل شده و هربار اطلاعاتی به آن push میشود و در نهایت این آرایه بازگشت داده خواهد شد:
اما با استفاده از Generators دیگر نیازی نیست تا یک آرایه برای جمع آوری این لیست تشکیل شود و به این ترتیب مصرف حافظهی برنامه کاهش خواهد یافت و همچنین اینبار این خروجی میتواند نامتنهاهی باشد (کاری که با استفاده از آرایههای معمولی قابل انجام نیست):
روشهایی برای خاموش کردن Generators
Generators علاوه بر متد next، دارای متدهای return و throw نیز هستند.
فراخوانی (generator.throw(error همانند این است که در بین کار، به متدی برخوردهایم که استثنایی را صادر کردهاست و سبب خاتمهی کار Generator شدهاست.
اگر متد return آنها فراخوانی شود، کار Generator پایان مییابد (خاصیت done شیء بازگشتی بلافاصله true میشود) و فراخوانی next، پس از آن، دیگر اثری نخواهد داشت:
مشابه آن حالتی است که در بین yieldها یک return وجود داشته باشد:
در این حالت نیز return سبب خاتمهی Generator شدهاست.
اگر به متد return خود Generator، پارامتری ارسال شود، این مقدار، مقدار نهایی بازگشت داده شده خواهد بود:
در این مثال (g.return(5 سبب خاتمهی Generator شدهاست و همچنین مقدار نهایی آنرا نیز تعیین کردهاست (عدد 5 بجای عدد 2).
در حالتیکه به متد return پارامتری ارسال میشود، قسمت finally بدنهی try/finally نوشته شده حتما اجرا خواهد شد و سپس کار خاتمه پیدا میکند:
در این مثال (g.return(6 پس از yield 2 فراخوانی شدهاست. اما همانطور که مشاهده میکنید، بدنهی finally کاملا اجرا شده و سپس Generator با عدد 6 خاتمه یافتهاست.
یک نمونه مثال ابتدایی از Generators را در کدهای زیر مشاهده میکنید:
function* generator () { yield 1; // pause yield 2; // pause yield 3; // pause yield 'done?'; // done }
let gen = generator(); // [object Generator] console.log(gen.next()); // Object {value: 1, done: false} console.log(gen.next()); // Object {value: 2, done: false} console.log(gen.next()); // Object {value: 3, done: false} console.log(gen.next()); // Object {value: 'done?', done: false} console.log(gen.next()); // Object {value: undefined, done: true} console.log(gen.next()); // Object {value: undefined, done: true}
و یا میتوان بجای فراخوانی دستی متد next، از حلقهی جدید for of نیز برای کار با آنها استفاده کرد:
for (let val of generator()) { console.log(val); // 1 // 2 // 3 // 'done?' }
function* random (max) { yield Math.floor(Math.random() * max) + 1; } function* random1_20 () { while (true) { yield* random(20); } } let rand = random1_20(); console.log(rand.next()); console.log(rand.next());
فقط در این حالت بجای yield معمولی از *yield استفاده میشود. از *yield برای کار با هر نوع Iterator ایی میتوان استفاده کرد:
function* multiplier (value) { yield value * 2; yield value * 3; yield value * 4; yield value * 5; } function* trailmix () { yield 0; yield* [1, 2]; yield* [...multiplier(2)]; yield* multiplier(3); }
کاهش مصرف حافظهی برنامه با استفاده از Generators
در مثال زیر، قرار است لیستی از rows بازگشت داده شود. در اینجا یک آرایه تشکیل شده و هربار اطلاعاتی به آن push میشود و در نهایت این آرایه بازگشت داده خواهد شد:
function splitIntoRows(icons, rowLength) { var rows = []; for (var i = 0; i < icons.length; i += rowLength) { rows.push(icons.slice(i, i + rowLength)); } return rows; }
function* splitIntoRows(icons, rowLength) { for (var i = 0; i < icons.length; i += rowLength) { yield icons.slice(i, i + rowLength); } }
روشهایی برای خاموش کردن Generators
Generators علاوه بر متد next، دارای متدهای return و throw نیز هستند.
فراخوانی (generator.throw(error همانند این است که در بین کار، به متدی برخوردهایم که استثنایی را صادر کردهاست و سبب خاتمهی کار Generator شدهاست.
اگر متد return آنها فراخوانی شود، کار Generator پایان مییابد (خاصیت done شیء بازگشتی بلافاصله true میشود) و فراخوانی next، پس از آن، دیگر اثری نخواهد داشت:
function* numbers () { yield 1 yield 2 yield 3 } var g = numbers() console.log(g.next()) // <- { done: false, value: 1 } console.log(g.return()) // <- { done: true } console.log(g.next()) // <- { done: true }, as we know
function* numbers () { yield 1 yield 2 return 3 yield 4 } console.log([...numbers()]) // <- [1, 2]
اگر به متد return خود Generator، پارامتری ارسال شود، این مقدار، مقدار نهایی بازگشت داده شده خواهد بود:
function* numbers () { yield 1 yield 2 return 3 yield 4 } var g = numbers() console.log(g.next()) // <- { done: false, value: 1 } console.log(g.return(5)) // <- { done: true, value: 5 } console.log(g.next()) // <- { done: true }
در حالتیکه به متد return پارامتری ارسال میشود، قسمت finally بدنهی try/finally نوشته شده حتما اجرا خواهد شد و سپس کار خاتمه پیدا میکند:
function* numbers () { yield 1 try { yield 2 } finally { yield 3 yield 4 } yield 5 } var g = numbers() console.log(g.next()) // <- { done: false, value: 1 } console.log(g.next()) // <- { done: false, value: 2 } console.log(g.return(6)) // <- { done: false, value: 3 } console.log(g.next()) // <- { done: false, value: 4 } console.log(g.next()) // <- { done: true, value: 6 }
اشتراکها
بررسی Native AOT در داتنت 8
Deep .NET - Ahead of Time Compilation (Native AOT) with Eric Erhardt
Scott Hanselman is joined by Eric Erhardt to go deep on all things Native AOT, that is right, Ahead of Time Compilation. Learn about everything Native AOT from start to finish and how .NET leverages this technology to make your apps and code super fast.
Chapters:
00:00:00 Intro
00:04:17 Understanding the Options and Restrictions of Publishing in .NET Apps
00:06:46 Limitations and Benefits of Native AOT
00:12:33 Development and Implementation of Web API AOT
00:16:28 Use of Create Small and Source Generators in Web Development
00:22:03 Role and Impact of Source Generators in Software Development
00:29:17 Application Performance Optimization and Role-Based Optimization in Web Development
00:33:27 Program Optimization Techniques and Trade-offs
00:37:28 Trade-offs and Considerations in Application Optimization
00:41:27 Understanding the Challenges and Limitations of Implementing AOT
00:46:34 Understanding and Implementing AOT
00:52:56 Understanding Model Streaming Extensions
00:55:50 C# 11 and AOT
01:03:49 Understanding and Addressing AOT Compatibility Issues
01:08:54 Understanding Trimming
01:10:35 Understanding and Addressing System Memory Data and Error Handling
01:16:16 Binary Data Compatibility and Source Generation in Visual Studio
01:24:25 Advanced Features
01:25:29 Wrap-up
دات نت 6 به همراه source generatorهای توکاری است که میتوانند کار serialization و deserialization نوع JSON را با کارآیی بسیار بیشتری انجام دهند؛ با آزمایشهایی که این بهبود را در حد 40 درصد سریعتر نسبت به حالت متداول آن نمایش میدهند و ... این مساله بسیار مهم است. از این جهت که این روزها، JSON را در همهجا مشاهده میکنیم؛ در Web APIها، در تنظیمات برنامهها، در ارسال پیامها بین برنامهها و غیره. بنابراین هرگونه بهبودی در زمینهی کارآیی serialization و deserialization آن، تاثیر بسیار قابل ملاحظهای را بر روی کارآیی کلی یک برنامه بجا خواهد گذاشت.
System.Text.Json source generator چیست؟
پایهی تمام اعمال serialization و deserialization در دات نت، استفاده از Reflection است که در زمینهی ارائهی برنامههایی با کارآیی بالا و با مصرف حافظهی پایین، بهینه عمل نمیکند. راهحل جایگزین استفاده از Reflection که در زمان اجرای برنامه رخ میدهد، به همراه دات نت 5 ارائه شد و source generators نام دارد. Source generators امکان تولید فایلهای #C را در زمان کامپایل برنامه میسر میکنند که نسبت به راهحل Reflection که در زمان اجرای برنامه فعال میشود، کارآیی بسیار بیشتری را ارائه میکنند. برای مثال به همراه دات نت 6، علاوه بر روش پیشفرض مبتنی بر Reflection ارائه شدهی توسط System.Text.Json، راه حل جدید امکان استفادهی از source generators توکار آن نیز پیش بینی شدهاست. کار اصلی آن، انجام تمام مراحلی است که پیشتر توسط Reflection در زمان اجرای برنامه صورت میگرفت، اینبار در زمان کامپایل برنامه و ارائهی آن به صورت از پیش آماده شده و مهیا.
مزایای این روش شامل موارد زیر است:
- بالا رفتن سرعت برنامه
- کاهش زمان آغاز اولیهی برنامه
- کاهش میزان حافظهی مورد نیاز برنامه
- عدم نیاز به استفادهی از System.Reflection و System.Reflection.Emit
- ارائهی Trim-compatible serialization که سبب کاهش اندازهی نهایی برنامه میشود. برای مثال در برنامههای Blazor میتوان با فعالسازی Trimming، کدهای استفاده نشده را از فایلهای بایناری نهایی حذف کرد. استفاده از source generators، با این روش سازگاری کاملی دارد.
مثالی از نحوهی کار با JSON در دات نت 6، توسط source generators آن
فرض کنید قصد داریم اعمال serialization و deserialization از نوع JSON را بر روی نمونههای کلاس زیر انجام دهیم:
اولین کاری که در این زمینه باید انجام شود، ایجاد یک کلاس خالی، با نامی دلخواه، اما مشتق شدهی از JsonSerializerContext است. در این حالت اخطارهایی را در IDE خود مبتنی بر نیاز به پیاده سازی تعدادی از متدهای این کلاس پایه دریافت میکنیم. اما ... ما قصد نداریم این متدها را پیاده سازی کنیم؛ Source generator قرار است اینکار را انجام دهد. به همین جهت این کلاس را partial تعریف کرده (تا source generator بتواند آنرا در فایلی دیگر تکمیل کند) و همچنین آنرا مزین به ویژگی JsonSerializable از نوع کلاسی که میخواهیم آنرا serialize کنیم، خواهیم کرد تا سبب فعال شدن source generator بر روی این کلاس شویم:
و ... همین! کدهای این کلاس partial توسط source generator در زمان کامپایل برنامه به صورت خودکار تولید و تکمیل میشوند.
پس از آن فقط کافی است MyJsonContext را به عنوان پارامتر متدهای جدید Serialize و یا Deserialize، به صورت زیر ارسال کنیم تا از آن استفاده شود:
متدهای جدید این API مبتنی بر source generators را در ادامه ملاحظه میکنید:
روش معرفی تنظیمات Serializer به Source generator
برای معرفی تنظیمات serialization و deserialization، برای مثال تهیهی خروجیهای CamelCase، میتوان از ویژگی JsonSourceGenerationOptions به صورت زیر استفاده کرد:
در این حالت مابقی کدها مانند قبل باقی خواهند ماند:
روش استفاده از JSON Source generators در برنامههای ASP.NET Core
در این نوع برنامهها، JsonSerializerContextها را میتوان توسط متد AddContext به صورت زیر به تنظیمات JSON برنامه معرفی کرد:
روش استفاده از JSON Source generators در برنامههای Blazor
البته در اینجا بیشتر منظور امکان استفادهی از آنها توسط HttpClient است که به صورت زیر توسط متد GetFromJsonAsync واقع در فضای نام System.Net.Http.Json، میسر شدهاست:
لیست کاملتر این API جدید به صورت زیر است:
System.Text.Json source generator چیست؟
پایهی تمام اعمال serialization و deserialization در دات نت، استفاده از Reflection است که در زمینهی ارائهی برنامههایی با کارآیی بالا و با مصرف حافظهی پایین، بهینه عمل نمیکند. راهحل جایگزین استفاده از Reflection که در زمان اجرای برنامه رخ میدهد، به همراه دات نت 5 ارائه شد و source generators نام دارد. Source generators امکان تولید فایلهای #C را در زمان کامپایل برنامه میسر میکنند که نسبت به راهحل Reflection که در زمان اجرای برنامه فعال میشود، کارآیی بسیار بیشتری را ارائه میکنند. برای مثال به همراه دات نت 6، علاوه بر روش پیشفرض مبتنی بر Reflection ارائه شدهی توسط System.Text.Json، راه حل جدید امکان استفادهی از source generators توکار آن نیز پیش بینی شدهاست. کار اصلی آن، انجام تمام مراحلی است که پیشتر توسط Reflection در زمان اجرای برنامه صورت میگرفت، اینبار در زمان کامپایل برنامه و ارائهی آن به صورت از پیش آماده شده و مهیا.
مزایای این روش شامل موارد زیر است:
- بالا رفتن سرعت برنامه
- کاهش زمان آغاز اولیهی برنامه
- کاهش میزان حافظهی مورد نیاز برنامه
- عدم نیاز به استفادهی از System.Reflection و System.Reflection.Emit
- ارائهی Trim-compatible serialization که سبب کاهش اندازهی نهایی برنامه میشود. برای مثال در برنامههای Blazor میتوان با فعالسازی Trimming، کدهای استفاده نشده را از فایلهای بایناری نهایی حذف کرد. استفاده از source generators، با این روش سازگاری کاملی دارد.
مثالی از نحوهی کار با JSON در دات نت 6، توسط source generators آن
فرض کنید قصد داریم اعمال serialization و deserialization از نوع JSON را بر روی نمونههای کلاس زیر انجام دهیم:
namespace Test { internal class Person { public string FirstName { get; set; } public string LastName { get; set; } } }
using System.Text.Json.Serialization; namespace Test { [JsonSerializable(typeof(Person))] internal partial class MyJsonContext : JsonSerializerContext { } }
پس از آن فقط کافی است MyJsonContext را به عنوان پارامتر متدهای جدید Serialize و یا Deserialize، به صورت زیر ارسال کنیم تا از آن استفاده شود:
Person person = new() { FirstName = "Jane", LastName = "Doe" }; byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(person, MyJsonContext.Default.Person); person = JsonSerializer.Deserialize(utf8Json, MyJsonContext.Default.Person);
متدهای جدید این API مبتنی بر source generators را در ادامه ملاحظه میکنید:
namespace System.Text.Json { public static class JsonSerializer { public static object? Deserialize(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerContext context) => ...; public static object? Deserialize(ReadOnlySpan<char> json, Type returnType, JsonSerializerContext context) => ...; public static object? Deserialize(string json, Type returnType, JsonSerializerContext context) => ...; public static object? Deserialize(ref Utf8JsonReader reader, Type returnType, JsonSerializerContext context) => ...; public static ValueTask<object?> DeserializeAsync(Stream utf8Json, Type returnType, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static ValueTask<TValue?> DeserializeAsync<TValue>(Stream utf8Json, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static TValue? Deserialize<TValue>(ReadOnlySpan<byte> utf8Json, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static TValue? Deserialize<TValue>(string json, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static TValue? Deserialize<TValue>(ReadOnlySpan<char> json, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static TValue? Deserialize<TValue>(ref Utf8JsonReader reader, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static string Serialize(object? value, Type inputType, JsonSerializerContext context) => ...; public static void Serialize(Utf8JsonWriter writer, object? value, Type inputType, JsonSerializerContext context) { } public static Task SerializeAsync(Stream utf8Json, object? value, Type inputType, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task SerializeAsync<TValue>(Stream utf8Json, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static byte[] SerializeToUtf8Bytes(object? value, Type inputType, JsonSerializerContext context) => ...; public static byte[] SerializeToUtf8Bytes<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static void Serialize<TValue>(Utf8JsonWriter writer, TValue value, JsonTypeInfo<TValue> jsonTypeInfo) { } public static string Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => ...; } }
برای معرفی تنظیمات serialization و deserialization، برای مثال تهیهی خروجیهای CamelCase، میتوان از ویژگی JsonSourceGenerationOptions به صورت زیر استفاده کرد:
using System.Text.Json.Serialization; namespace Test { [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] [JsonSerializable(typeof(Person))] internal partial class MyJsonContext : JsonSerializerContext { } }
string json = JsonSerializer.Serialize(person, MyJsonContext.Default.Person); Person person = JsonSerializer.Deserialize(json, MyJsonContext.Default.Person);
روش استفاده از JSON Source generators در برنامههای ASP.NET Core
در این نوع برنامهها، JsonSerializerContextها را میتوان توسط متد AddContext به صورت زیر به تنظیمات JSON برنامه معرفی کرد:
services.AddControllers().AddJsonOptions(options => options.AddContext<MyJsonContext>());
روش استفاده از JSON Source generators در برنامههای Blazor
البته در اینجا بیشتر منظور امکان استفادهی از آنها توسط HttpClient است که به صورت زیر توسط متد GetFromJsonAsync واقع در فضای نام System.Net.Http.Json، میسر شدهاست:
[JsonSerializable(typeof(WeatherForecast[]))] internal partial class MyJsonContext : JsonSerializerContext { } @code { private WeatherForecast[] forecasts; private static JsonSerializerOptions Options = new(JsonSerializerDefaults.Web); private static MyJsonContext Context = new MyJsonContext(Options); protected override async Task OnInitializedAsync() { forecasts = await Http.GetFromJsonAsync("sample-data/weather.json", Context.WeatherForecastArray); } }
namespace System.Net.Http.Json { public static partial class HttpClientJsonExtensions { public static Task<object?> GetFromJsonAsync(this HttpClient client, string? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<object?> GetFromJsonAsync(this HttpClient client, System.Uri? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, string? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, string? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; } public static partial class HttpContentJsonExtensions { public static Task<object?> ReadFromJsonAsync(this HttpContent content, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<T?> ReadFromJsonAsync<T>(this HttpContent content, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; } }
به C# 12 و داتنت 8، ویژگی «آزمایشی» جدیدی به نام Interceptors اضافه شدهاست که به آن «monkey patching» هم میگویند. هدف از آن، جایگزین کردن یک پیاده سازی، با پیاده سازی دیگری است. به این ترتیب توسعه دهندگان داتنتی میتوانند فراخوانی متدهایی خاص را رهگیری کرده (interception) و سپس آنرا به فراخوانی یک پیاده سازی جدید، هدایت کنند.
Interceptor چیست؟
از زمان ارائهی NET 8 preview 6 SDK. به بعد، امکان رهگیری هر متدی از کدهای برنامه، به داتنت اضافه شدهاست؛ به همین جهت از واژهی Interceptor/رهگیر در اینجا استفاده میشود. خود تیم داتنت از این قابلیت در جهت بازنویسی پویای قسمتهایی از کدهای زیرساخت داتنت که از Reflection استفاده میکنند، با نگارشهای کامپایل شدهی مختص به برنامهی شما، کمک میگیرند. به این ترتیب سرعت و کارآیی برنامههای داتنت 8، بهبود قابل ملاحظهای را پیدا کردهاند. برای مثال ahead-of-time compilation (AOT) در داتنت 8 و ASP.NET Core 8x بر اساس این ویژگی پیاده سازی شدهاست. این ویژگی جدید، مکمل source generators است که در نگارشهای پیشین داتنت ارائه شده بود.
بررسی Interceptors با تهیهی یک مثال ساده
فرض کنید میخواهیم فراخوانی متد GetText زیر را رهگیری کرده و سپس آنرا با نمونهی دیگری جایگزین کنیم:
برای اینکار ابتدا نیاز است یک فایل جدید را به نام InterceptsLocationAttribute.cs با محتوای زیر به پروژه اضافه کرد:
همانطور که در مقدمهی بحث هم عنوان شد، این ویژگی هنوز آزمایشی است و نهایی نشده و ویژگی فوق نیز هنوز به داتنت اضافه نشدهاست. به همین جهت فعلا باید آنرا به صورت دستی به پروژه اضافه کرد و احتمالا در نگارشهای بعدی داتنت، امضای آن تغییر خواهد کرد ... یا حتی ممکن است بطور کامل حذف شود!
سپس فرض کنید فراخوانی متد GetText در فایل Program.cs برنامه به صورت زیر انجام شدهاست:
یعنی متد GetText، در سطر چهارم و کاراکتر 20 ام آن فراخوانی شدهاست. این اعداد مهم هستند!
در ادامه از این اطلاعات در رهگیر سفارشی زیر استفاده خواهیم کرد:
این رهگیر که به صورت متدی الحاقی برای کلاس InterceptorsSample دربرگیرندهی متد GetText تهیه میشود، کار جایگزینی فراخوانی آنرا در سطر چهارم و کاراکتر 20 ام فایل Program.cs انجام میدهد. امضای پارامترهای این متد، باید با امضای پارامترهای متد رهگیری شده، یکی باشد.
اکنون اگر برنامه را اجرا کنیم ... با خطای زیر مواجه میشویم:
عنوان میکند که این ویژگی آزمایشی است و باید فایل csproj. را به صورت زیر تغییر داد تا بتوان از آن استفاده نمود:
اینبار برنامه کامپایل شده و اجرا میشود. در این حالت خروجی جدید برنامه، خروجی تامین شدهی توسط رهگیر سفارشی ما است:
سؤال: آیا رهگیری انجام شده، در زمان کامپایل انجام میشود یا در زمان اجرا؟
برای این مورد میتوان به Low-Level C# code تولیدی مراجعه کرد. برای مشاهدهی یک چنین کدهایی میتوانید از منوی Tools->IL Viewer برنامهی Rider استفاده کرده و در برگهی ظاهر شده، گزینهی Low-Level C# آنرا انتخاب نمائید:
همانطور که مشاهده میکنید، این رهگیری و جایگزینی، در زمان کامپایل انجام شده و کامپایلر، بهطور کامل نحوهی فراخوانی متد GetText اصلی را به متد رهگیر ما تغییر داده و بازنویسی کردهاست.
سؤال: آیا این قابلیت واقعا کاربردی است؟!
اکنون شاید این سؤال مطرح شود که ... واقعا چه کسی قرار است مسیر کامل یک فایل، شماره سطر و شماره ستون فراخوانی متدی را به اینگونه در اختیار سیستم رهگیری قرار دهد؟! آیا واقعا این قابلیت، یک قابلیت کاربردی و مناسب است؟!
اینجا است که اهمیت source generators مشخص میشود. توسط source generators دسترسی کاملی به syntax trees وجود دارد و همچنین یکسری اطلاعات تکمیلی مانند FilePath و سپس CSharpSyntaxNodeها که دسترسی به دادههای متد ()GetLocation را دارند که مکان دقیق سطر و ستونهای فراخوانیها را مشخص میکند.
کاربردهای فعلی رهگیرها در دات نت 8
در دات نت 8، این موارد با استفاده از رهگیرها بهینه سازی شده و سرعت آنها افزایش یافتهاند:
- فراخوانیهایی که تمام اطلاعات آنها در زمان کامپایل فراهم است، مانند Regex.IsMatch(@"a+b+") که از یک الگوی ثابت و مشخص استفاده میکند، رهگیری شده و پیاده سازی آن با کدی استاتیک، جایگزین میشود.
- در ASP.NET Minimal API، استفاده از lambda expressions جهت ارائهی تعاریفی مانند:
مرسوم است. این نوع فراخوانیها نیز توسط رهگیرها برای جایگزینی handler آنها با کدهای استاتیک، جهت بالابردن کارآیی و کاهش تخصیصهای حافظه انجام میشود.
- بهبود کارآیی foreach loops جهت استفاده از ریاضیات برداری و SIMD در صورت امکان.
- بهبود کارآیی تزریق وابستگیها، زمانیکه به تعاریف مشخصی مانند ()<provider.Register<MyService ختم میشود.
- بجای استفاده از expression trees در زمان اجرای برنامه، اکنون میتوان کدهای SQL معادل را در زمان کامپایل برنامه تولید کرد.
- بهبود کارآیی Serializers، زمانیکه از یک نوع مشخص مانند ()<Serialize<MyType استفاده میشود و کامپایلر میتواند آنرا با کدهای زمان کامپایل، جایگزین کند.
محدودیتهای رهگیرها در داتنت 8
- رهگیرهای داتنت 8 فقط با متدها کار میکنند.
- مسیر ارائه شده حتما باید یک مسیر کامل و مشخص باشد. یعنی اگر این قطعه کد، به سیستم دیگری منتقل شود، کامپایل نخواهد شد و امکان ارائهی مسیرهای نسبی وجود ندارد.
- امضای متدها، حتما باید یکی باشد. یعنی نمیتوان یک رهگیر جنریک را تعریف کرد.
Interceptor چیست؟
از زمان ارائهی NET 8 preview 6 SDK. به بعد، امکان رهگیری هر متدی از کدهای برنامه، به داتنت اضافه شدهاست؛ به همین جهت از واژهی Interceptor/رهگیر در اینجا استفاده میشود. خود تیم داتنت از این قابلیت در جهت بازنویسی پویای قسمتهایی از کدهای زیرساخت داتنت که از Reflection استفاده میکنند، با نگارشهای کامپایل شدهی مختص به برنامهی شما، کمک میگیرند. به این ترتیب سرعت و کارآیی برنامههای داتنت 8، بهبود قابل ملاحظهای را پیدا کردهاند. برای مثال ahead-of-time compilation (AOT) در داتنت 8 و ASP.NET Core 8x بر اساس این ویژگی پیاده سازی شدهاست. این ویژگی جدید، مکمل source generators است که در نگارشهای پیشین داتنت ارائه شده بود.
بررسی Interceptors با تهیهی یک مثال ساده
فرض کنید میخواهیم فراخوانی متد GetText زیر را رهگیری کرده و سپس آنرا با نمونهی دیگری جایگزین کنیم:
namespace CS8Tests; public class InterceptorsSample { public string GetText(string text) { return $"{text}, World!"; } }
namespace System.Runtime.CompilerServices; [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] public sealed class InterceptsLocationAttribute : Attribute { public InterceptsLocationAttribute(string filePath, int line, int character) { } }
سپس فرض کنید فراخوانی متد GetText در فایل Program.cs برنامه به صورت زیر انجام شدهاست:
using CS8Tests; var example = new InterceptorsSample(); var text = example.GetText("Hello"); Console.WriteLine(text); //Hello, World!
در ادامه از این اطلاعات در رهگیر سفارشی زیر استفاده خواهیم کرد:
using System.Runtime.CompilerServices; namespace CS8Tests; public static class MyInterceptor { [InterceptsLocation("C:\\Path\\To\\CS8Tests\\Program.cs", 4, 20)] public static string InterceptorMethod(this InterceptorsSample example, string text) { return $"{text}, DNT!"; } }
اکنون اگر برنامه را اجرا کنیم ... با خطای زیر مواجه میشویم:
error CS9137: The 'interceptors' experimental feature is not enabled in this namespace. Add '<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);CS8Tests</InterceptorsPreviewNamespaces>' to your project.
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <!--<NoWarn>Test001</NoWarn>--> <InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);CS8Tests</InterceptorsPreviewNamespaces> </PropertyGroup> </Project>
Hello, DNT!
سؤال: آیا رهگیری انجام شده، در زمان کامپایل انجام میشود یا در زمان اجرا؟
برای این مورد میتوان به Low-Level C# code تولیدی مراجعه کرد. برای مشاهدهی یک چنین کدهایی میتوانید از منوی Tools->IL Viewer برنامهی Rider استفاده کرده و در برگهی ظاهر شده، گزینهی Low-Level C# آنرا انتخاب نمائید:
using CS8Tests; using System; using System.Runtime.CompilerServices; [CompilerGenerated] internal class Program { private static void <Main>$(string[] args) { Console.WriteLine(new InterceptorsSample().InterceptorMethod("Hello")); } public Program() { base..ctor(); } }
سؤال: آیا این قابلیت واقعا کاربردی است؟!
اکنون شاید این سؤال مطرح شود که ... واقعا چه کسی قرار است مسیر کامل یک فایل، شماره سطر و شماره ستون فراخوانی متدی را به اینگونه در اختیار سیستم رهگیری قرار دهد؟! آیا واقعا این قابلیت، یک قابلیت کاربردی و مناسب است؟!
اینجا است که اهمیت source generators مشخص میشود. توسط source generators دسترسی کاملی به syntax trees وجود دارد و همچنین یکسری اطلاعات تکمیلی مانند FilePath و سپس CSharpSyntaxNodeها که دسترسی به دادههای متد ()GetLocation را دارند که مکان دقیق سطر و ستونهای فراخوانیها را مشخص میکند.
کاربردهای فعلی رهگیرها در دات نت 8
در دات نت 8، این موارد با استفاده از رهگیرها بهینه سازی شده و سرعت آنها افزایش یافتهاند:
- فراخوانیهایی که تمام اطلاعات آنها در زمان کامپایل فراهم است، مانند Regex.IsMatch(@"a+b+") که از یک الگوی ثابت و مشخص استفاده میکند، رهگیری شده و پیاده سازی آن با کدی استاتیک، جایگزین میشود.
- در ASP.NET Minimal API، استفاده از lambda expressions جهت ارائهی تعاریفی مانند:
app.MapGet("/products", handler: (int? page, int? pageLength, MyDb db) => { ... })
- بهبود کارآیی foreach loops جهت استفاده از ریاضیات برداری و SIMD در صورت امکان.
- بهبود کارآیی تزریق وابستگیها، زمانیکه به تعاریف مشخصی مانند ()<provider.Register<MyService ختم میشود.
- بجای استفاده از expression trees در زمان اجرای برنامه، اکنون میتوان کدهای SQL معادل را در زمان کامپایل برنامه تولید کرد.
- بهبود کارآیی Serializers، زمانیکه از یک نوع مشخص مانند ()<Serialize<MyType استفاده میشود و کامپایلر میتواند آنرا با کدهای زمان کامپایل، جایگزین کند.
محدودیتهای رهگیرها در داتنت 8
- رهگیرهای داتنت 8 فقط با متدها کار میکنند.
- مسیر ارائه شده حتما باید یک مسیر کامل و مشخص باشد. یعنی اگر این قطعه کد، به سیستم دیگری منتقل شود، کامپایل نخواهد شد و امکان ارائهی مسیرهای نسبی وجود ندارد.
- امضای متدها، حتما باید یکی باشد. یعنی نمیتوان یک رهگیر جنریک را تعریف کرد.
In addition to bug fixes and foundation work for larger features, this preview includes updates to ensure that converters and comparers are handled by type mapping and to support using converters with value generators.
اشتراکها
TypeScript 3.6 Beta منتشر شد
اشتراکها