Share via


Riktlinjer för beroendeinmatning

Den här artikeln innehåller allmänna riktlinjer och metodtips för att implementera beroendeinmatning i .NET-program.

Utforma tjänster för beroendeinmatning

När du utformar tjänster för beroendeinmatning:

  • Undvik tillståndskänsliga, statiska klasser och medlemmar. Undvik att skapa globalt tillstånd genom att utforma appar så att de använder singleton-tjänster i stället.
  • Undvik direkt instansiering av beroende klasser inom tjänster. Direkt instansiering kopplar koden till en viss implementering.
  • Gör tjänsterna små, välräknade och enkelt testade.

Om en klass har många inmatade beroenden kan det vara ett tecken på att klassen har för många ansvarsområden och bryter mot SRP (Single Responsibility Principle). Försök att omstrukturera klassen genom att flytta en del av dess ansvarsområden till nya klasser.

Avyttring av tjänster

Containern ansvarar för rensning av typer som skapas och anropar Dispose instanser IDisposable . Tjänster som löses från containern bör aldrig tas bort av utvecklaren. Om en typ eller fabrik registreras som en singleton tas singletonen bort automatiskt av containern.

I följande exempel skapas tjänsterna av tjänstcontainern och tas bort automatiskt:

namespace ConsoleDisposable.Example;

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

Ovanstående disponibel är avsedd att ha en tillfällig livslängd.

namespace ConsoleDisposable.Example;

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

Ovanstående disponibel är avsedd att ha en begränsad livslängd.

namespace ConsoleDisposable.Example;

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

Ovanstående disponibel är avsedd att ha en singleton-livslängd.

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>();
}

Felsökningskonsolen visar följande exempelutdata när du har kört:

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

Tjänster som inte har skapats av tjänstcontainern

Ta följande kod som exempel:

// Register example service in IServiceCollection
builder.Services.AddSingleton(new ExampleService());

I koden ovan:

  • Instansen ExampleService skapas inte av tjänstcontainern.
  • Ramverket tar inte bort tjänsterna automatiskt.
  • Utvecklaren ansvarar för att ta bort tjänsterna.

IDisposable-vägledning för tillfälliga och delade instanser

Tillfällig, begränsad livslängd

Scenario

Appen kräver en IDisposable instans med en tillfällig livslängd för något av följande scenarier:

  • Instansen matchas i rotomfånget (rotcontainern).
  • Instansen ska tas bort innan omfånget upphör.

Lösning

Använd fabriksmönstret för att skapa en instans utanför det överordnade omfånget. I den här situationen skulle appen vanligtvis ha en Create metod som anropar den slutliga typens konstruktor direkt. Om den slutliga typen har andra beroenden kan fabriken:

Delad instans, begränsad livslängd

Scenario

Appen kräver en delad IDisposable instans för flera tjänster, men instansen IDisposable bör ha en begränsad livslängd.

Lösning

Registrera instansen med en begränsad livslängd. Använd IServiceScopeFactory.CreateScope för att skapa en ny IServiceScope. Använd omfånget IServiceProvider för att hämta nödvändiga tjänster. Ta bort omfånget när det inte längre behövs.

Allmänna IDisposable riktlinjer

  • Registrera IDisposable inte instanser med en tillfällig livslängd. Använd fabriksmönstret i stället.
  • Lös inte IDisposable instanser med en tillfällig eller begränsad livslängd i rotomfånget. Det enda undantaget är om appen skapar/återskapar IServiceProvideroch tar bort , men det här är inte ett idealiskt mönster.
  • Att ta emot ett IDisposable beroende via DI kräver inte att mottagaren implementerar IDisposable sig själv. Mottagaren av IDisposable beroendet bör inte anropa Dispose det beroendet.
  • Använd omfång för att styra tjänsternas livslängd. Omfång är inte hierarkiska och det finns ingen särskild anslutning mellan omfången.

Mer information om resursrensning finns i Implementera en Dispose metod eller Implementera en DisposeAsync metod. Tänk också på de disponibla tillfälliga tjänster som fångas upp av containerscenariot när det gäller resursrensning.

Ersättning av standardtjänstcontainer

Den inbyggda tjänstcontainern är utformad för att uppfylla behoven i ramverket och de flesta konsumentappar. Vi rekommenderar att du använder den inbyggda containern om du inte behöver en specifik funktion som den inte stöder, till exempel:

  • Egenskapsinmatning
  • Inmatning baserat på namn (.NET 7 och tidigare versioner endast. Mer information finns i Nyckelade tjänster.)
  • Underordnade containrar
  • Anpassad livslängdshantering
  • Func<T> stöd för lat initiering
  • Konventionsbaserad registrering

Följande containrar från tredje part kan användas med ASP.NET Core-appar:

Trådsäkerhet

Skapa trådsäkra singleton-tjänster. Om en singleton-tjänst har ett beroende av en tillfällig tjänst kan den tillfälliga tjänsten också kräva trådsäkerhet beroende på hur den används av singletonen.

Fabriksmetoden för en singleton-tjänst, till exempel det andra argumentet till AddSingleton<TService>(IServiceCollection, Func<IServiceProvider,TService>), behöver inte vara trådsäker. Precis som en typkonstruktor (static) kommer den garanterat bara att anropas en gång av en enda tråd.

Rekommendationer

  • async/await och Task baserad tjänstmatchning stöds inte. Eftersom C# inte stöder asynkrona konstruktorer använder du asynkrona metoder när tjänsten har lösts synkront.
  • Undvik att lagra data och konfiguration direkt i tjänstcontainern. En användares kundvagn bör till exempel vanligtvis inte läggas till i tjänstcontainern. Konfigurationen bör använda alternativmönstret. På samma sätt bör du undvika "datahållare"-objekt som bara finns för att tillåta åtkomst till ett annat objekt. Det är bättre att begära det faktiska objektet via DI.
  • Undvik statisk åtkomst till tjänster. Undvik till exempel att samla in IApplicationBuilder.ApplicationServices som ett statiskt fält eller en egenskap för användning någon annanstans.
  • Håll DI-fabrikerna snabba och synkrona.
  • Undvik att använda tjänstlokaliserarmönstret. Anropa till exempel inte GetService för att hämta en tjänstinstans när du kan använda DI i stället.
  • En annan variant av tjänstlokaliseraren att undvika är att mata in en fabrik som löser beroenden vid körning. Båda dessa metoder blandar Inversion av kontrollstrategier .
  • Undvik anrop till BuildServiceProvider när du konfigurerar tjänster. Samtal BuildServiceProvider sker vanligtvis när utvecklaren vill lösa en tjänst när en annan tjänst registreras. Använd i stället en överlagring som innehåller av IServiceProvider den anledningen.
  • Disponibla tillfälliga tjänster samlas in av containern för bortskaffande. Detta kan förvandlas till en minnesläcka om det löses från containern på den översta nivån.
  • Aktivera omfångsvalidering för att se till att appen inte har singletons som samlar in begränsade tjänster. Mer information finns i Omfångsverifiering.

Precis som med alla uppsättningar med rekommendationer kan det uppstå situationer där det krävs att du ignorerar en rekommendation. Undantag är sällsynta, mestadels specialfall inom själva ramverket.

DI är ett alternativ till åtkomstmönster för statiska/globala objekt. Du kanske inte kan dra nytta av fördelarna med DI om du blandar det med statisk objektåtkomst.

Exempel på antimönster

Förutom riktlinjerna i den här artikeln finns det flera antimönster som du bör undvika. Några av dessa antimönster är lärdomar från att utveckla själva körningen.

Varning

Det här är exempel på antimönster, kopiera inte koden, använd inte dessa mönster och undvik dessa mönster till varje pris.

Disponibla tillfälliga tjänster som samlas in av en container

När du registrerar tillfälliga tjänster som implementerar IDisposablekommer DI-containern som standard att lagra dessa referenser och inte Dispose() på dem förrän containern tas bort när programmet stoppas om de har lösts från containern eller tills omfånget tas bort om de har lösts från ett omfång. Detta kan förvandlas till en minnesläcka om den matchas från containernivå.

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

I föregående antimönster instansieras och rotas 1 000 ExampleDisposable objekt. De tas inte bort förrän instansen serviceProvider tas bort.

Mer information om felsökning av minnesläckor finns i Felsöka en minnesläcka i .NET.

Async DI-fabriker kan orsaka dödlägen

Termen "DI-fabriker" refererar till de överlagringsmetoder som finns när du anropar Add{LIFETIME}. Det finns överlagringar som accepterar en Func<IServiceProvider, T> var T är tjänsten registreras och parametern heter implementationFactory. implementationFactory Kan anges som ett lambda-uttryck, en lokal funktion eller en metod. Om fabriken är asynkron och du använder Task<TResult>.Resultorsakar detta ett dödläge.

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

I föregående kod implementationFactory ges ett lambda-uttryck där brödtexten anropar Task<TResult>.Result en Task<Bar> returnerande metod. Detta orsakar ett dödläge. Metoden GetBarAsync emulerar helt enkelt en asynkron arbetsåtgärd med Task.Delayoch anropar GetRequiredService<T>(IServiceProvider)sedan .

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

Mer information om asynkron vägledning finns i Asynkron programmering: Viktig information och råd. Mer information om felsökning av dödlägen finns i Felsöka ett dödläge i .NET.

När du kör det här antimönstret och dödläget inträffar kan du visa de två trådar som väntar från Fönstret Parallella staplar i Visual Studio. Mer information finns i Visa trådar och aktiviteter i fönstret Parallella staplar.

Captive dependency (Captive Dependency)

Termen "captive dependency" myntades av Mark Seemann och refererar till felkonfigurationen av tjänstlivslängden, där en längre tjänst har en kortare livslängd som tjänstfångst.

Anti-pattern: Captive dependency. Do not copy!

I föregående kod Foo registreras som en singleton och Bar är begränsad – vilket på ytan verkar giltigt. Tänk dock på implementeringen av Foo.

namespace DependencyInjection.AntiPatterns;

public class Foo(Bar bar)
{
}

Objektet Foo kräver ett Bar objekt, och eftersom Foo är en singleton och Bar är begränsad – det här är en felkonfiguration. Som det är Foo skulle bara instansieras en gång, och det skulle hålla fast Bar vid under sin livslängd, vilket är längre än den avsedda begränsade livslängden Barför . Du bör överväga att verifiera omfång genom att skicka validateScopes: true till BuildServiceProvider(IServiceCollection, Boolean). När du verifierar omfången får du ett InvalidOperationException med ett meddelande som liknar "Det går inte att använda den begränsade tjänsten 'Bar' från singleton 'Foo'.".

Mer information finns i Omfångsverifiering.

Begränsad tjänst som singleton

Om du inte skapar ett omfång eller inom ett befintligt omfång när du använder begränsade tjänster blir tjänsten en singleton.

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

I föregående kod Bar hämtas inom en IServiceScope, vilket är korrekt. Antimönstret är hämtningen av Bar utanför omfånget och variabeln namnges avoid för att visa vilket exempel på hämtning som är felaktigt.

Se även