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 DisposeIDisposable . 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:

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 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, 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 öğesini IServiceProvider 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.

Anti-pattern: Transient disposables without dispose. Do not copy!

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.

Anti-pattern: Deadlock with async factory. Do not copy!

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).

Anti-pattern: Deadlock with async factory inner issue. Do not copy!

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.

Anti-pattern: Captive dependency. Do not copy!

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 Bardaha 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.

Anti-pattern: Scoped service becomes singleton. Do not copy!

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 .

Ayrıca bkz.