فرض کنید در برنامهی خود، یک سرویس جنریک را طراحی کردهاید. برای مثال خود ASP.NET Core به همراه سرویس جنریک <ILogger<T است و اگر برای نمونه بخواهیم آنرا در سازندهی کنترلری مانند ValuesController تزریق کنیم، نحوهی تعریف آن به صورت <ILogger<ValuesController خواهد بود. هر چند تنظیمات این سرویس پیشتر انجام شدهاست، اما اگر بخواهیم آنرا به همین نحو <ILogger<T به متدهایی مانند services.AddScoped معرفی کنیم، کار نمیکند؛ نمونهی دیگری از این دست Generic Repositoryها هستند:
// does not work: services.AddScoped<IGenericRepository<T>,EFRepository<T>>();
نحوهی معرفی سرویسهای جنریک نامحدود (Open Generics و یا Unbound Generics) به سیستم تزریق وابستگیها
اگر بخواهیم یک سرویس جنریک را به سیستم تزریق وابستگیهای برنامههای NET Core. به نحو متداولی معرفی کنیم، نیاز است به ازای تک تک Tهای میسر و تعریف شدهی در برنامه، اینکار صورت گیرد:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IStore<User>, SqlStore<User>>();
services.AddScoped<IStore<Invoice>, SqlStore<Invoice>>();
services.AddScoped<IStore<Payment>, SqlStore<Payment>>();
// ...
}
و در اینجا به ازای هر T یا موجودیت جدیدی در برنامه، نیاز است یک سطر دیگر را نیز تعریف کرد. خوشبختانه در این سیستم، امکان تعریف جنریکهای نامحدود و باز نیز وجود دارد:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(typeof(IStore<>), typeof(SqlStore<>));
}
به این ترتیب تمام سطرهایی که پیشتر تعریف کردیم، تبدیل به یک سطر فوق میشوند. در اینجا از Overload غیرجنریک متد AddScoped استفاده شده؛ به همین جهت از واژهی کلیدی typeof برای معرفی نوعهای جنریک باز کمک گرفته شدهاست. ذکر <> نیز به معنای تفسیر و وهله سازی هر نوع درخواستی رسیده، در زمان اجرا میباشد.
محدودیت کار کردن با جنریکهای نامحدود در سیستم تزریق وابستگیها
با تعریف تک سطر فوق، هر چند برنامه بدون مشکل کامپایل میشود، اما اگر در زمان اجرای برنامه، <IStore<T ای را درخواست کنید که میسر نباشد (در خواست هر نوعی در زمان اجرا با جنریکهای باز معرفی شده، میسر است)، یک استثنای زمان اجرا را دریافت میکنید؛ برای مثال اگر نوع T به کلاسها محدود شده باشد و در قسمتی از برنامه، <IStore<int درخواست شود. هرچند این موارد با یکبار آزمایش برنامه، قابل یافت شدن و رفع میباشند.
کتابخانهی کمکی Scrutor نیز از جنریکهای باز پشتیبانی میکند
در
قسمت قبل نحوهی اسکن اسمبلیهای برنامه را توسط کتابخانهی کمکی Scrutor بررسی کردیم. این کتابخانه امکان یافتن و فیلتر کلاسها و معرفی آنها را به سیستم تزریق وابستگیها، بر اساس ویژگی جنریکهای باز نیز دارا است:
services.Scan(scan => scan
.FromAssemblyOf<CombinedService>()
.AddClasses(x=> x.AssignableTo(typeof(IOpenGeneric<>))) // Can close generic types
.AsMatchingInterface())
ساده سازی «مثال 2: وهله سازی در صورت نیاز وابستگیهای یک سرویس به کمک Lazy loading» قسمت ششم با جنریکهای نامحدود
در
قسمت ششم نحوهی تعریف پیشنیازهای وهله سازی به تاخیر افتاده را با استفاده از کلاس Lazy بررسی کردیم:
services.AddTransient<IOrderHandler, OrderHandlerLazy>();
services.AddTransient<IAccounting, Accounting>()
.AddTransient(serviceProvider => new Lazy<IAccounting>(() => serviceProvider.GetRequiredService<IAccounting>()));
services.AddTransient<ISales, Sales>()
.AddTransient(serviceProvider => new Lazy<ISales>(() => serviceProvider.GetRequiredService<ISales>()));
- در اینجا در ابتدا تمام سرویسها (حتی آنهایی که قرار است به صورت Lazy استفاده شوند) یکبار به صورت متداولی معرفی میشوند.
- سپس سرویسهایی که قرار است به صورت Lazy نیز واکشی شوند، بار دیگر توسط روش factory registration با وهله سازی new Lazy از نوع سرویس مدنظر و فراهم آوردن پیاده سازی آن با استفاده از serviceProvider.GetRequiredService، مجددا معرفی خواهند شد.
اگر به شرط دوم دقت کنید، <new Lazy<IAccounting و <new Lazy<ISales، دقیقا مانند همان سرویسهای جنریک <IStore<User و <IStore<Invoice تعریف شدهاند. یعنی نیاز است به ازای هر T ممکن در برنامه، یکبار <new Lazy<T را نیز به سیستم تزریق وابستگیها معرفی کرد. بنابراین تمام این تعریفهای اضافی را میتوان با یک سطر جنریک نامحدود زیر جایگزین و خلاصه کرد:
services.AddTransient(typeof(Lazy<>), typeof(LazyFactory<>));
و نکتهی جالب آن، نحوهی تعریف قسمت factory متدهایی مانند AddTransient در اینجا است که به صورت زیر قابل پیاده سازی است:
public class LazyFactory<T> : Lazy<T> where T : class
{
public LazyFactory(IServiceProvider provider)
: base(() => provider.GetRequiredService<T>())
{
}
}