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 Dispose på IDisposable-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.
// 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:
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.
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:
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.
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.
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å.
I föregående antimönster instansieras och rotas 1 000 ExampleDisposable objekt. De tas inte bort förrän instansen serviceProvider tas bort.
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.
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 .
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.
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.
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.
Källan för det här innehållet finns på GitHub, där du även kan skapa och granska ärenden och pull-begäranden. Se vår deltagarguide för mer information.
Feedback om .NET
.NET är ett öppen källkod projekt. Välj en länk för att ge feedback:
Förstå och implementera beroendeinmatning i en ASP.NET Core-app. Använd ASP.NET Cores inbyggda tjänstcontainer för att hantera beroenden. Registrera tjänster med tjänstcontainern.
Lär dig hur du använder beroendeinjektion i dina .NET-appar med denna omfattande handledning. Följ med i den här pragmatiska guiden för att förstå DI i C#.
Lär dig hur du använder beroendeinmatning i dina .NET-appar. Upptäck hur du registrerar tjänster, definierar tjänstlivslängder och expressberoenden i C#.
Lär dig hur du använder beroendeinmatning (DI) i dina .NET-appar med det här enkla exemplet. Följ med i den här pragmatiska guiden för att förstå GRUNDerna för DI i C#.