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:
- Ta emot en IServiceProvider i konstruktorn.
- Använd ActivatorUtilities.CreateInstance för att instansiera instansen utanför containern när du använder containern för dess beroenden.
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
ochTask
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 avIServiceProvider
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å.
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.
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 .
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.
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 Bar
fö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.
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.