Läs på engelska

Dela 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 beroendeinjektion:

  • 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 den skapar och anropar DisposeIDisposable-instanser. 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()");
}

Den föregående engångsartikeln ä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 engångsartikel ä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()");
}

Den föregående engångsartikeln är utformad för att ha en unik 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 löses inom rotomfånget (rotcontainern).
  • Instansen ska avvecklas 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 erhålla nödvändiga tjänster. Ta bort scopet 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änstlösning 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 injicera en fabrik som hanterar beroenden vid körningstid. Båda dessa metoder blandar Inversion of Control-strategier.
  • Undvik anrop till BuildServiceProvider när du konfigurerar tjänster. Att anropa BuildServiceProvider sker vanligtvis när utvecklaren vill återlösa en tjänst vid registreringen av en annan tjänst. Använd i stället en överlagring som innehåller IServiceProvider av 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 i utvecklingen av körmiljöerna.

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. Det kan leda till en minnesläcka om det löses på containernivå.

Antimönster: Tillfälliga engångsartiklar utan bortskaffande. Kopiera inte!

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" hänvisar till de överlagringsmetoder som finns när du kallar på Add{LIFETIME}. Det finns överlagringar som accepterar en Func<IServiceProvider, T>, där T är tjänsten som 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.

Antimönster: Dödläge med asynkron fabrik. Kopiera inte!

I den föregående koden tilldelas implementationFactory ett lambda-uttryck där kroppen anropar Task<TResult>.Result på en metod som returnerar Task<Bar>. 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 med async factory inner issue. Kopiera inte!

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ådarna som väntar från Parallel Stacks window i Visual Studio. För mer information, se Visa trådar och aktiviteter i fönstret Parallella staplar.

Bunden beroende

Termen "captive dependency" myntades av Mark Seemann och avser en felkonfiguration av tjänsternas livslängder, där en tjänst med längre livslängd är beroende av en med kortare livslängd.

Anti-mönster: Beroende i fångenskap. Kopiera inte!

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 skulle Foo bara instansieras en gång, och det skulle hålla fast Bar under sin livslängd, vilket är längre än den avsedda begränsade livslängden för Bar. Du bör överväga att verifiera omfång genom att skicka validateScopes: true till BuildServiceProvider(IServiceCollection, Boolean). När du validerar rättighetsomfången får du ett InvalidOperationException med ett meddelande som liknar "Det går inte att använda begränsad tjänst 'Bar' från singletonen 'Foo'.".

Mer information finns i Omfångsverifiering.

Tjänst med begränsat omfång som singleton

När du använder begränsade tjänster och inte skapar ett omfång eller befinner dig inom ett befintligt omfång, blir tjänsten en singleton.

Antimönster: Begränsad tjänst blir singleton. Kopiera inte!

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