Bağımlılık ekleme yönergeleri
Bu makalede ,NET uygulamalarında bağımlılık ekleme uygulamak için genel yönergeler ve en iyi yöntemler sağlanmaktadır.
Bağımlılık ekleme için hizmetler tasarlama
Bağımlılık ekleme için hizmetler tasarlarken:
- Durum bilgisi olan statik sınıflardan ve üyelerden kaçının. Bunun yerine tekil hizmetleri kullanacak uygulamalar tasarlayarak genel durum oluşturmaktan kaçının.
- Hizmetler içindeki bağımlı sınıfların doğrudan örneğini oluşturmaktan kaçının. Doğrudan örnekleme, kodu belirli bir uygulamayla eşler.
- Hizmetleri küçük, iyi faktörlü ve kolayca test edilmiş hale getirin.
Bir sınıfın çok fazla eklenmiş bağımlılığı varsa, sınıfın çok fazla sorumlulukları olduğunu ve Tek Sorumluluk İlkesi'ni (SRP) ihlal ettiğinin bir işareti olabilir. Bazı sorumluluklarını yeni sınıflara taşıyarak sınıfını yeniden düzenlemeyi deneme.
Hizmetlerin elden çıkarılması
Kapsayıcı, oluşturduğu türlerin temizlenmesinden sorumludur ve örnekleri çağırır Dispose IDisposable . Kapsayıcıdan çözümlenen hizmetler hiçbir zaman geliştirici tarafından atılmamalıdır. Bir tür veya fabrika tekil olarak kayıtlıysa kapsayıcı, tekliyi otomatik olarak atılır.
Aşağıdaki örnekte, hizmetler hizmet kapsayıcısı tarafından oluşturulur ve otomatik olarak atılır:
namespace ConsoleDisposable.Example;
public sealed class TransientDisposable : IDisposable
{
public void Dispose() => Console.WriteLine($"{nameof(TransientDisposable)}.Dispose()");
}
Önceki atılabilir öğe, geçici bir yaşam süresine sahip olmak üzere tasarlanmıştır.
namespace ConsoleDisposable.Example;
public sealed class ScopedDisposable : IDisposable
{
public void Dispose() => Console.WriteLine($"{nameof(ScopedDisposable)}.Dispose()");
}
Önceki atılabilir öğe, kapsamı belirlenmiş bir yaşam süresine sahip olmak üzere tasarlanmıştır.
namespace ConsoleDisposable.Example;
public sealed class SingletonDisposable : IDisposable
{
public void Dispose() => Console.WriteLine($"{nameof(SingletonDisposable)}.Dispose()");
}
Önceki atılabilir kullanım ömrü tek kullanımlık olacak şekilde tasarlanmıştır.
using ConsoleDisposable.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddTransient<TransientDisposable>();
builder.Services.AddScoped<ScopedDisposable>();
builder.Services.AddSingleton<SingletonDisposable>();
using IHost host = builder.Build();
ExemplifyDisposableScoping(host.Services, "Scope 1");
Console.WriteLine();
ExemplifyDisposableScoping(host.Services, "Scope 2");
Console.WriteLine();
await host.RunAsync();
static void ExemplifyDisposableScoping(IServiceProvider services, string scope)
{
Console.WriteLine($"{scope}...");
using IServiceScope serviceScope = services.CreateScope();
IServiceProvider provider = serviceScope.ServiceProvider;
_ = provider.GetRequiredService<TransientDisposable>();
_ = provider.GetRequiredService<ScopedDisposable>();
_ = provider.GetRequiredService<SingletonDisposable>();
}
Hata ayıklama konsolu çalıştırıldıktan sonra aşağıdaki örnek çıkışı gösterir:
Scope 1...
ScopedDisposable.Dispose()
TransientDisposable.Dispose()
Scope 2...
ScopedDisposable.Dispose()
TransientDisposable.Dispose()
info: Microsoft.Hosting.Lifetime[0]
Application started.Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: .\configuration\console-di-disposable\bin\Debug\net5.0
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
SingletonDisposable.Dispose()
Hizmet kapsayıcısı tarafından oluşturulmayan hizmetler
Aşağıdaki kodu inceleyin:
// Register example service in IServiceCollection
builder.Services.AddSingleton(new ExampleService());
Önceki kodda:
- Örnek
ExampleService
, hizmet kapsayıcısı tarafından oluşturulmaz . - Çerçeve, hizmetleri otomatik olarak atmıyor.
- Geliştirici, hizmetleri yok etme sorumluluğundadır.
Geçici ve paylaşılan örnekler için IDisposable kılavuzu
Geçici, sınırlı yaşam süresi
Senaryo
Uygulama, aşağıdaki senaryolardan biri için geçici yaşam süresine sahip bir IDisposable örnek gerektirir:
- Örnek kök kapsamda (kök kapsayıcı) çözümlenir.
- Kapsam sona ermeden önce örnek atılmalıdır.
Çözüm
Üst kapsamın dışında bir örnek oluşturmak için fabrika desenini kullanın. Bu durumda, uygulama genellikle son türün oluşturucusunun doğrudan çağıran bir Create
yöntemine sahip olur. Son türün başka bağımlılıkları varsa fabrika şunları yapabilir:
- Oluşturucusunda bir IServiceProvider alır.
- Kapsayıcıyı bağımlılıkları için kullanırken örneği kapsayıcının dışında örneklemek için kullanın ActivatorUtilities.CreateInstance .
Paylaşılan örnek, sınırlı yaşam süresi
Senaryo
Uygulama, birden çok hizmet arasında paylaşılan IDisposable bir örnek gerektirir, ancak IDisposable örneğin kullanım ömrü sınırlıdır.
Çözüm
Örneği kapsamlı bir yaşam süresiyle kaydedin. Yeni IServiceScopebir oluşturmak için kullanınIServiceScopeFactory.CreateScope. Gerekli hizmetleri almak için kapsamları IServiceProvider kullanın. Artık gerekli olmadığında kapsamı atın.
Genel IDisposable
yönergeler
- Örnekleri geçici bir yaşam süresiyle kaydetmeyin IDisposable . Bunun yerine fabrika desenini kullanın.
- Kök kapsamda geçici veya kapsamlı bir yaşam süresine sahip örnekleri çözümlemeyin IDisposable . Bunun tek istisnası, uygulamanın uygulamasını oluşturması/yeniden oluşturması ve atılmasıdır IServiceProvider, ancak bu ideal bir desen değildir.
- DI aracılığıyla bağımlılık IDisposable almak için alıcının kendisini uygulaması IDisposable gerekmez. Bağımlılığın alıcısı IDisposable bu bağımlılığı çağırmamalıdır Dispose .
- Hizmetlerin kullanım ömrünü denetlemek için kapsamları kullanın. Kapsamlar hiyerarşik değildir ve kapsamlar arasında özel bir bağlantı yoktur.
Kaynak temizleme hakkında daha fazla bilgi için bkz. Yöntem uygulama Dispose
veya Yöntem uygulamaDisposeAsync
. Ayrıca, kaynak temizlemeyle ilgili olarak kapsayıcı senaryosu tarafından yakalanan Atılabilir geçici hizmetleri de göz önünde bulundurun.
Varsayılan hizmet kapsayıcısı değiştirme
Yerleşik hizmet kapsayıcısı, çerçevenin ve çoğu tüketici uygulamasının gereksinimlerini karşılayacak şekilde tasarlanmıştır. Aşağıdakiler gibi desteklemediği belirli bir özelliğe ihtiyacınız yoksa yerleşik kapsayıcıyı kullanmanızı öneririz:
- Özellik ekleme
- Ada göre ekleme (yalnızca.NET 7 ve önceki sürümler. Daha fazla bilgi için bkz . Anahtarlı hizmetler.)
- Alt kapsayıcılar
- Özel yaşam süresi yönetimi
Func<T>
gecikmeli başlatma desteği- Kural tabanlı kayıt
Aşağıdaki üçüncü taraf kapsayıcıları ASP.NET Core uygulamalarıyla kullanılabilir:
İş parçacığı güvenliği
İş parçacığı güvenli tekil hizmetler oluşturun. Tek bir hizmetin geçici bir hizmete bağımlılığı varsa, geçici hizmet tekil hizmet tarafından nasıl kullanıldığına bağlı olarak iş parçacığı güvenliği de gerektirebilir.
AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>) için ikinci bağımsız değişken gibi tek bir hizmetin fabrika yönteminin iş parçacığı güvenli olması gerekmez. Bir tür oluşturucu (static
) oluşturucu gibi, tek bir iş parçacığı tarafından yalnızca bir kez çağrılacağı garanti edilir.
Öneriler
async/await
veTask
tabanlı hizmet çözümlemesi desteklenmez. C# zaman uyumsuz oluşturucuları desteklemediğinden, hizmeti zaman uyumlu bir şekilde çözümledikten sonra zaman uyumsuz yöntemleri kullanın.- Verileri ve yapılandırmayı doğrudan hizmet kapsayıcısında depolamaktan kaçının. Örneğin, bir kullanıcının alışveriş sepeti genellikle hizmet kapsayıcısına eklenmemelidir. Yapılandırma, seçenekler desenini kullanmalıdır. Benzer şekilde, yalnızca başka bir nesneye erişime izin vermek için var olan "veri sahibi" nesnelerden kaçının. Gerçek öğeyi DI aracılığıyla istemek daha iyidir.
- Hizmetlere statik erişimden kaçının. Örneğin, IApplicationBuilder.ApplicationServices'i başka bir yerde kullanmak üzere statik bir alan veya özellik olarak yakalamaktan kaçının.
- DI fabrikalarını hızlı ve zaman uyumlu tutun.
- Hizmet bulucu düzenini kullanmaktan kaçının. Örneğin, bunun yerine DI'yi kullanabileceğiniz bir hizmet örneği almak için çağırmayın GetService .
- Başka bir hizmet bulucu varyasyonu, çalışma zamanında bağımlılıkları çözümleyen bir fabrika eklemektir. Bu uygulamaların her ikisi de Inversion of Control stratejilerini bir araya getirebilir.
- Hizmetleri yapılandırırken çağrısı yapmaktan BuildServiceProvider kaçının. Arama
BuildServiceProvider
genellikle geliştirici başka bir hizmeti kaydederken bir hizmeti çözümlemek istediğinde gerçekleşir. Bunun yerine, bu nedenle öğesiniIServiceProvider
içeren bir aşırı yükleme kullanın. - Atılabilir geçici hizmetler, kapsayıcı tarafından imha edilmek üzere yakalanır . Bu, üst düzey kapsayıcıdan çözümlenirse bellek sızıntısına dönüşebilir.
- Uygulamanın kapsamı belirlenmiş hizmetleri yakalayan tekillere sahip olmadığından emin olmak için kapsam doğrulamasını etkinleştirin. Daha fazla bilgi için bkz . Kapsam doğrulaması.
Tüm öneri kümelerinde olduğu gibi, bir öneriyi yoksaymanın gerekli olduğu durumlarla karşılaşabilirsiniz. Özel durumlar nadirdir, çoğunlukla çerçevenin kendi içinde özel durumlardır.
DI, statik/genel nesne erişim desenlerine bir alternatiftir . Statik nesne erişimiyle karıştırırsanız DI'nin avantajlarını fark edemeyebilirsiniz.
Örnek anti-desenler
Bu makaledeki yönergelere ek olarak, kaçınmanız gereken çeşitli anti-desenler vardır. Bu anti-desenlerden bazıları, çalışma zamanlarını geliştirmenin kendileri tarafından öğrenilir.
Uyarı
Bunlar örnek anti-desenlerdir, kodu kopyalamaz , bu desenleri kullanmaz ve her ne pahasına olursa olsun bu desenlerden kaçının.
Kapsayıcı tarafından yakalanan tek kullanımlık geçici hizmetler
uygulayan Geçici hizmetleri kaydettiğinizde, varsayılan olarak DI kapsayıcısı, kapsayıcıdan çözümlendiklerinde uygulama durduğunda kapsayıcı atılana kadar veya kapsam bir kapsamdan çözümlendiyse kapsam atılana kadar bu başvuruları tutar ve bu başvurulardan birini tutmazDispose(). IDisposable Kapsayıcı düzeyinden çözümlenirse bu bir bellek sızıntısına dönüşebilir.
Yukarıdaki anti-desende 1.000 ExampleDisposable
nesne örneği oluşturulur ve köklenir. Bunlar, örnek atılana serviceProvider
kadar atılmaz.
Bellek sızıntılarında hata ayıklama hakkında daha fazla bilgi için bkz . .NET'te bellek sızıntısında hata ayıklama.
Zaman uyumsuz DI fabrikaları kilitlenmelere neden olabilir
"DI fabrikaları" terimi çağrılırken Add{LIFETIME}
var olan aşırı yükleme yöntemlerini ifade eder. Hizmetin kaydedildiği bir konumu T
kabul eden Func<IServiceProvider, T>
aşırı yüklemeler vardır ve parametresi olarak adlandırılırimplementationFactory
. implementationFactory
bir lambda ifadesi, yerel işlev veya yöntem olarak sağlanabilir. Fabrika zaman uyumsuzsa ve kullanıyorsanız Task<TResult>.Result, bu kilitlenmeye neden olur.
Yukarıdaki kodda, gövdesinin implementationFactory
döndüren bir yöntemi çağırdığı Task<TResult>.Result bir Task<Bar>
lambda ifadesi verilir. Bu kilitlenmeye neden olur. GetBarAsync
yöntemi ile zaman uyumsuz bir iş işlemine Task.Delayöykünerek öğesini çağırırGetRequiredService<T>(IServiceProvider).
Zaman uyumsuz yönergeler hakkında daha fazla bilgi için bkz . Zaman uyumsuz programlama: Önemli bilgiler ve öneriler. Kilitlenmelerde hata ayıklama hakkında daha fazla bilgi için bkz . .NET'te kilitlenme hatalarını ayıklama.
Bu anti-deseni çalıştırdığınızda ve kilitlenme oluştuğunda, Visual Studio'nun Paralel Yığınlar penceresinden bekleyen iki iş parçacığını görüntüleyebilirsiniz. Daha fazla bilgi için bkz . Paralel Yığınlar penceresinde iş parçacıklarını ve görevleri görüntüleme.
Tutsak bağımlılık
"Tutsak bağımlılık" terimi Mark Seemann tarafından kullanılmıştır ve daha uzun süreli bir hizmetin daha kısa süreli hizmet esaretine sahip olduğu hizmet yaşam sürelerinin yanlış yapılandırılmasına işaret eder.
Yukarıdaki kodda, Foo
tekil olarak kaydedilir ve Bar
kapsamı belirlenmiştir. Bu kod, yüzey üzerinde geçerli görünür. Ancak uygulamasını göz önünde bulundurun Foo
.
namespace DependencyInjection.AntiPatterns;
public class Foo(Bar bar)
{
}
Foo
Nesne bir Bar
nesne gerektirir ve çünkü Foo
tekildir ve Bar
kapsamı belirlenmiştir; bu yanlış yapılandırmadır. Olduğu gibi, Foo
yalnızca bir kez örneği oluşturulur ve hedeflenen kapsamı belirlenmiş yaşam süresinden Bar
daha uzun olan ömrü boyunca tutunırBar
. kapsamına geçirerek validateScopes: true
kapsamları doğrulamayı BuildServiceProvider(IServiceCollection, Boolean)düşünmelisiniz. Kapsamları doğruladığınızda , "'Foo' singletonundan 'Bar' kapsamlı hizmet kullanılamaz" gibi bir ileti alırsınız InvalidOperationException .
Daha fazla bilgi için bkz . Kapsam doğrulaması.
Tekil olarak kapsamlı hizmet
Kapsamı belirlenmiş hizmetleri kullanırken, kapsam oluşturmuyorsanız veya mevcut bir kapsam içindeyseniz, hizmet tek bir kapsam haline gelir.
Yukarıdaki kodda, Bar
doğru olan bir IServiceScopeiçinde alınır. Anti-pattern, kapsamın dışından Bar
alma işlemidir ve değişken, hangi örnek alma işleminin yanlış olduğunu göstermek için adlandırılır avoid
.