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 فقط با متدها کار میکنند.
- مسیر ارائه شده حتما باید یک مسیر کامل و مشخص باشد. یعنی اگر این قطعه کد، به سیستم دیگری منتقل شود، کامپایل نخواهد شد و امکان ارائهی مسیرهای نسبی وجود ندارد.
- امضای متدها، حتما باید یکی باشد. یعنی نمیتوان یک رهگیر جنریک را تعریف کرد.