Aracılığıyla paylaş


Bağımlılık enjeksiyonu yönergeleri

Bu makalede, .NET uygulamalarında bağımlılık ekleme (DI) uygulamaya yönelik genel yönergeler ve en iyi yöntemler sağlanmaktadır.

Bağımlılık enjeksiyonu için hizmetler tasarlama

Bağımlılık ekleme için hizmetler tasarlarken:

  • Durum bilgisine sahip statik sınıflardan ve üyelerden kaçının. Bunun yerine tekil hizmetleri kullanacak uygulamalar tasarlayarak genel durum oluşturmaktan kaçının.
  • Servisler içindeki bağımlı sınıfların doğrudan örneklendirilmesinden 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. Yeni sınıflara bazı sorumluluklar taşıyarak sınıfı yeniden düzenlemeyi dene.

Hizmetlerin devri

Kapsayıcı, oluşturduğu türlerin temizlenmesinden sorumludur ve Dispose örnekleri üzerinde IDisposable çağrısı yapar. Kapsayıcıdan sağlanan hizmetler hiçbir zaman geliştirici tarafından yok edilmemelidir. Bir tür veya fabrika tekil olarak kayıtlıysa, kapsayıcı tekili otomatik olarak atar.

Aşağıdaki örnekte, hizmetler hizmet kapsayıcısı tarafından oluşturulur ve otomatik olarak bertaraf edilir.

namespace ConsoleDisposable.Example;

public sealed class TransientDisposable : IDisposable
{
    public void Dispose() => Console.WriteLine($"{nameof(TransientDisposable)}.Dispose()");
}

Daha önce kullanılan tek kullanımlık, 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 tek kullanımlık öğ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()");
}

Tek kullanımlık, tek seferlik kullanım ömrüne sahip 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şturulmuş değildir.
  • Çerçeve, hizmetleri otomatik olarak kaldırmı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 konteyneri) çözümlenir.
  • Kapsam sona ermeden önce örnek atılmalıdır.

Solution

Ana kapsama alanının dışında bir örnek oluşturmak için fabrika desenini kullanın. Bu durumda, uygulamanın genellikle nihai türün kurucusunu doğrudan çağıran bir Create yöntemi vardır. Son türün başka bağımlılıkları varsa fabrika şunları yapabilir:

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.

Solution

Örneği kapsamlı bir yaşam süresiyle kaydedin. IServiceScopeFactory.CreateScope kullanarak yeni bir IServiceScope oluşturun. Gerekli hizmetleri almak için kapsamın IServiceProvider kullanarak faydalanın. Artık gerekli olmadığında kapsamı atın.

Genel IDisposable yönergeler

  • Örnekleri geçici bir yaşam süresiyle kaydetmeyin IDisposable . Artık kullanımda olmadığında çözülen hizmetin el ile atılabilmesi için bunun yerine fabrika düzenini kullanın.
  • Kök kapsamda IDisposable geçici veya belirli bir kapsam ömrüne sahip örnekleri çözümlemeyin. Bunun tek istisnası, uygulamanın IServiceProvider oluşturması veya yeniden oluşturması ve atmasıdır, ancak bu ideal bir desen değildir.
  • DI aracılığıyla bir bağımlılık IDisposable almak, alıcının IDisposable kendisini uygulamasını gerektirmez. 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. Bir Dispose yöntemi uygulayın veya Bir DisposeAsync yöntemi uygulayın. Ayrıca, kaynak temizleme açısından kapsayıcı tarafından yakalanan tekrar kullanılmayan geçici hizmetler senaryosunu da 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
  • Alt kapsayıcılar
  • Özel ömür yönetimi
  • Func<T> gecikmeli başlatma desteği
  • Konvansiyon 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. Tekil bir hizmetin (örneğin, AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>) için ikinci bağımsız değişken olarak) fabrika yöntemi, iş parçacığı açısından güvenli olmak zorunda değildir. Bir tür oluşturucu gibi (static), tek bir iş parçacığı tarafından yalnızca bir kez çağrılacağı garanti edilir.

Ayrıca, yerleşik .NET bağımlılık ekleme kapsayıcısından hizmetleri çözümleme işlemi iş parçacığı açısından güvenlidir. Bir IServiceProvider veya IServiceScope oluşturulduktan sonra, hizmetleri birden çok iş parçacığından eşzamanlı olarak çözümlemek güvenlidir.

Uyarı

DI kapsayıcısının iş parçacığı güvenliği, yalnızca hizmetlerin oluşturulmasının ve çözülmesinin güvenli olduğunu garanti eder. Çözümlenen hizmet örnekleri, kendi başına iş parçacığı güvenliğini sağlamaz. Paylaşılan değiştirilebilir durumu tutan hizmetlerin tümü (özellikle tekiller), kendilerine eşzamanlı olarak erişilmesi durumunda kendi eşitleme mantığını uygulamalıdır.

Recommendations

  • async/await ve Task 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, başka bir yerde kullanmak üzere statik alan veya özellik olarak IApplicationBuilder.ApplicationServices kapsamaktan kaçının.
  • DI fabrikalarını hızlı ve eş zamanlı tutun.
  • Hizmet bulucu düzenini kullanmaktan kaçının. Örneğin, bir hizmet örneği elde etmek için GetService çağırmak yerine DI kullanın.
  • Kaçınılması gereken 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 BuildServiceProvider çağrı yapmaktan 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 nedeniyle IServiceProvider içeren bir aşırı yükleme kullanın.
  • Tek kullanımlık geçici hizmetler, bertaraf edilmek üzere kapsayıcı tarafından 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ı.
  • Tek kullanımlık ömrü yalnızca oluşturması veya genel olarak paylaşılmaları pahalı olan kendi durumlarına sahip hizmetler için kullanın. Kendi durumu olmayan hizmetler için tek kullanımlık kullanım ömrü kullanmaktan kaçının. Çoğu .NET IoC kapsayıcısı varsayılan kapsam olarak "Geçici" kullanır. Singletonların dikkat edilmesi gereken noktaları ve dezavantajları:
    • İş parçacığı güvenliği: Tekil iş parçacığı güvenli bir şekilde uygulanmalıdır.
    • Bağlama: İlgisiz istekleri birleştirebilir.
    • Test zorlukları: Paylaşılan durum ve bağlantı birim testlerini zorlaştırabilir.
    • Bellek etkisi: Bir singleton, geniş bir nesne grafiğini uygulamanın ömrü boyunca bellekte canlı tutabilir.
    • Hataya dayanıklılık: Tekil nesne veya bağımlılık ağacındaki herhangi bir bileşen başarısız olursa, kolayca toparlanamaz.
    • Yapılandırma yeniden yükleme: Singleton'lar genellikle yapılandırma değerlerinin "çalışırken yeniden yüklenmesini" desteklemez.
    • Kapsam sızıntısı: Bir singleton, kapsamlı veya geçici bağımlılıkları yanlışlıkla yakalayabilir, onları fiilen singleton'a dönüştürebilir ve istenmeyen yan etkilere neden olabilir.
    • Başlatma yükü: Bir hizmeti çözümlerken IoC kapsayıcısının tek örneği araması gerekir. Henüz yoksa, iş parçacığı güvenli bir şekilde oluşturması gerekir. Buna karşılık, durum bilgisi olmayan geçici bir hizmet oluşturmak ve yok etmek çok ucuzdur.

Tüm öneri kümelerinde olduğu gibi, bir öneriyi yoksaymanın gerekli olduğu durumlarla karşılaşabilirsiniz. Özel durumlar nadirdir ve ç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ştirirken elde edilen öğrenimlerdir.

Uyarı

Bunlar örnek anti-desenlerdir. Kodu kopyalamayın, bu desenleri kullanmayın 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 IDisposable hizmetleri kaydettiğinizde, DI kapsayıcısı varsayılan olarak bu başvurularda yer alır. Uygulama kapsayıcıdan çözümlendiyse, kapsayıcı durdurulduğunda kapsayıcı atılana kadar veya kapsam bir kapsamdan çözümlendiyse kapsam atılana kadar bunları atamaz. Kapsayıcı düzeyinden çözümlenirse bellek sızıntısı oluşabilir.

Anti-pattern: Dispose edilmeyen geçici nesneler. Kopyalamayın!

Yukarıdaki anti-desende 1.000 ExampleDisposable nesne örneği oluşturulur ve köklenir. Bunlar, serviceProvider örneği kaldırılana kadar yok edilmez.

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.

Asenkron DI oluşturucular kilitlenmelere neden olabilir

"DI fabrikaları" terimi, Add{LIFETIME} çağrılırken mevcut olan aşırı yükleme yöntemlerini ifade eder. Bir Func<IServiceProvider, T> kabul eden ve hizmetin kaydedildiği yer olan T içeren aşırı yüklemeler vardır ve parametresi implementationFactory olarak adlandırılır. implementationFactory bir lambda ifadesi, yerel işlev veya yöntem olarak sağlanabilir. Eğer fabrika zaman uyumsuzsa ve Task<TResult>.Result kullanıyorsanız, kilitlenmeye neden olur.

Desen önleme: Zaman uyumsuz fabrika ile kilitlenme. Kopyalamayın!

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, Task.Delay ile zaman uyumsuz bir iş işlemine basitçe öykünür ve ardından GetRequiredService<T>(IServiceProvider) öğesini çağırır.

Anti-pattern: Zaman uyumsuz fabrika içi sorunla meydana gelen kilitlenme. Kopyalamayın!

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, Paralel Yığınlar penceresinde iş parçacıklarını ve görevleri görüntüleme kısmına bakınız.

Tutsak bağımlılık

Mark Seemann tarafından kullanılan "tutsak bağımlılık" terimi, daha uzun süreli bir hizmetin daha kısa süreli bir hizmet esareti barındırdığı hizmet ömrünün yanlış yapılandırılmasını ifade eder.

Karşı-deseni: Esir bağımlılık. Kopyalamayın!

Yukarıdaki kodda, Foo tekil olarak kaydedilir ve Bar kapsamı belirlenmiştir. Bu kod, yüzey üzerinde geçerli görünür. Ancak Foo'nin uygulanmasını göz önünde bulundurun.

namespace DependencyInjection.AntiPatterns;

public class Foo(Bar bar)
{
}

Foo nesnesi bir Bar nesnesi gerektirir ve Foo tekil bir yapı olduğundan ve Bar ise belirli bir kapsama sahip olduğundan, bu bir yapılandırma hatasıdır. Olduğu gibi, Foo yalnızca bir kez örneklenir ve yaşam süresi boyunca Bar'i tutar; bu, Bar'nin hedeflenen kapsamlı yaşam süresinden daha uzundur. kapsamları doğrulamak için validateScopes: true'yi BuildServiceProvider(IServiceCollection, Boolean)'ye geçirerek değerlendirin. Kapsamları doğruladığınızda, "'Foo' singletonundan 'Bar' kapsamlı hizmet tüketilemez" gibi bir ileti alırsınız InvalidOperationException .

Daha fazla bilgi için bkz . Kapsam doğrulaması.

Tekil olarak kapsamlanmış hizmet

Kapsam belirlenmiş hizmetleri kullanırken, bir skop oluşturmuyorsanız veya mevcut bir skop içindeyseniz, hizmet bir singleton haline gelir.

Yanlış desen: Kapsamlı servis singleton hale gelir. Kopyalamayın!

Yukarıdaki kodda, Bar doğru olan bir IServiceScopeiçinde alınır. Anti-pattern, Bar'nin kapsam dışında alınmasıdır ve hangi örnek alma işleminin yanlış olduğunu göstermek için değişkene avoid adı verilmiştir.

Ayrıca bakınız