Dela via


.NET-beroendeinmatning

.NET stöder designmönstret för beroendeinmatning (DI), vilket är en teknik för att uppnå inversion av kontroll (IoC) mellan klasser och deras beroenden. Beroendeinmatning i .NET är en inbyggd del av ramverket, tillsammans med konfiguration, loggning och alternativmönstret.

Ett beroende är ett objekt som ett annat objekt är beroende av. Granska följande MessageWriter klass med en Write metod som andra klasser är beroende av:

public class MessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}

En klass kan skapa en instans av MessageWriter klassen för att använda sin Write metod. I följande exempel MessageWriter är klassen ett beroende av Worker klassen:

public class Worker : BackgroundService
{
    private readonly MessageWriter _messageWriter = new();

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

Klassen skapar och är direkt beroende av MessageWriter klassen. Hårdkodade beroenden, till exempel i föregående exempel, är problematiska och bör undvikas av följande skäl:

  • Om du vill ersätta MessageWriter med en annan implementering måste du ändra Worker klassen.
  • Om MessageWriter har beroenden måste klassen Worker också konfigurera dem. I ett stort projekt med flera klasser beroende på MessageWriterblir konfigurationskoden utspridd över appen.
  • Den här implementeringen är svår att enhetstesta. Appen bör använda en modell- eller stub-klass MessageWriter , vilket inte är möjligt med den här metoden.

Beroendeinjektion löser följande problem genom:

  • Användning av ett gränssnitt eller en basklass för att abstrahera beroendeimplementeringen.
  • Registrering av beroendet i en tjänstcontainer. .NET tillhandahåller en inbyggd tjänstcontainer, IServiceProvider. Tjänsterna registreras vanligtvis vid appens start och läggs till i en IServiceCollection. När alla tjänster har lagts till använder du BuildServiceProvider för att skapa tjänstcontainern.
  • Inmatning av tjänsten i konstruktorn för den klass där den används. Ramverket tar på sig ansvaret att skapa en instans av beroendet och ta bort det när det inte längre behövs.

Gränssnittet definierar IMessageWriter till exempel Write metoden:

namespace DependencyInjection.Example;

public interface IMessageWriter
{
    void Write(string message);
}

Det här gränssnittet implementeras av en konkret typ, MessageWriter:

namespace DependencyInjection.Example;

public class MessageWriter : IMessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}

Exempelkoden registrerar IMessageWriter tjänsten med betongtypen MessageWriter. Metoden AddSingleton registrerar tjänsten med en singleton-livslängd, appens livslängd. Tjänstens livslängd beskrivs senare i den här artikeln.

using DependencyInjection.Example;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHostedService<Worker>();
builder.Services.AddSingleton<IMessageWriter, MessageWriter>();

using IHost host = builder.Build();

host.Run();

I föregående kod, exempelappen:

  • Skapar en värdappsbyggareinstans.

  • Konfigurerar tjänsterna genom att registrera:

    • Som Worker en värdbaserad tjänst. Mer information finns i Worker Services i .NET.
    • Gränssnittet IMessageWriter som en singleton-tjänst med en motsvarande implementering av MessageWriter klassen.
  • Skapar värden och kör den.

Värden innehåller providern för beroendeinmatningstjänsten. Den innehåller också alla andra relevanta tjänster som krävs för att automatiskt instansiera Worker och tillhandahålla motsvarande IMessageWriter implementering som ett argument.

namespace DependencyInjection.Example;

public sealed class Worker(IMessageWriter messageWriter) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

Med hjälp av DI-mönstret:

  • Använder inte betongtypen MessageWriter, bara IMessageWriter det gränssnitt som implementeras. Detta gör det enkelt att ändra den implementering som arbetartjänsten använder utan att ändra arbetstjänsten.
  • Skapar inte en instans av MessageWriter. DI-containern skapar instansen.

Implementeringen av gränssnittet kan förbättras med hjälp av IMessageWriter det inbyggda loggnings-API:et:

namespace DependencyInjection.Example;

public class LoggingMessageWriter(
    ILogger<LoggingMessageWriter> logger) : IMessageWriter
{
    public void Write(string message) =>
        logger.LogInformation("Info: {Msg}", message);
}

Den uppdaterade AddSingleton metoden registrerar den nya IMessageWriter implementeringen:

builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();

Typen HostApplicationBuilder (builder) är en del av NuGet-paketet Microsoft.Extensions.Hosting .

LoggingMessageWriter beror på ILogger<TCategoryName>, som den begär i konstruktorn. ILogger<TCategoryName> är en ramverksbaserad tjänst.

Det är inte ovanligt att använda beroendeinmatning på ett kedjat sätt. Varje begärt beroende begär i sin tur sina egna beroenden. Containern löser beroendena i diagrammet och returnerar den fullständigt lösta tjänsten. Den kollektiva uppsättningen beroenden som måste lösas kallas vanligtvis ett beroendeträd, beroendediagram eller objektdiagram.

Containern löser ILogger<TCategoryName> problemet genom att dra nytta av (generiska) öppna typer, vilket eliminerar behovet av att registrera alla (generiska) konstruerade typer.

Med terminologi för beroendeinmatning, en tjänst:

  • Är vanligtvis ett objekt som tillhandahåller en tjänst till andra objekt, till exempel tjänsten IMessageWriter .
  • Är inte relaterat till en webbtjänst, även om tjänsten kan använda en webbtjänst.

Ramverket tillhandahåller ett robust loggningssystem. Implementeringarna IMessageWriter som visas i föregående exempel visar grundläggande DI, inte loggning. De flesta appar ska inte behöva skriva loggare. Följande kod visar hur du använder standardloggningen, som endast kräver att den Worker registreras som en värdbaserad tjänst AddHostedService:

public sealed class Worker(ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

            await Task.Delay(1_000, stoppingToken);
        }
    }
}

Med hjälp av föregående kod behöver du inte uppdatera Program.cs eftersom ramverket tillhandahåller loggning.

Flera identifieringsregler för konstruktor

När en typ definierar mer än en konstruktor har tjänstleverantören logik för att avgöra vilken konstruktor som ska användas. Konstruktorn med de flesta parametrar där typerna är DI-matchbara väljs. Överväg följande C#-exempeltjänst:

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(ILogger<ExampleService> logger)
    {
        // omitted for brevity
    }

    public ExampleService(FooService fooService, BarService barService)
    {
        // omitted for brevity
    }
}

I föregående kod antar du att loggning har lagts till och kan lösas från tjänstleverantören, men att typerna FooService och BarService inte kan lösas. Konstruktorn med parametern ILogger<ExampleService> löser instansen ExampleService . Även om det finns en konstruktor som definierar fler parametrar är typerna FooService och BarService inte DI-matchbara.

Om det finns tvetydigheter vid identifiering av konstruktorer utlöses ett undantag. Överväg följande C#-exempeltjänst:

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(ILogger<ExampleService> logger)
    {
        // omitted for brevity
    }

    public ExampleService(IOptions<ExampleOptions> options)
    {
        // omitted for brevity
    }
}

Varning

Koden ExampleService med tvetydiga DI-matchningsbara typparametrar utlöser ett undantag. Gör inte detta – det är avsett att visa vad som menas med "tvetydiga DI-lösbara typer".

I föregående exempel finns det tre konstruktorer. Den första konstruktorn är parameterlös och kräver inga tjänster från tjänstleverantören. Anta att både loggning och alternativ har lagts till i DI-containern och är DI-matchningsbara tjänster. När DI-containern försöker matcha ExampleService typen utlöser den ett undantag eftersom de två konstruktorerna är tvetydiga.

Undvik tvetydighet genom att definiera en konstruktor som accepterar båda DI-matchbara typerna i stället:

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(
        ILogger<ExampleService> logger,
        IOptions<ExampleOptions> options)
    {
        // omitted for brevity
    }
}

Registrera grupper av tjänster med tilläggsmetoder

Microsoft Extensions använder en konvention för att registrera en grupp relaterade tjänster. Konventionen är att använda en enda Add{GROUP_NAME} tilläggsmetod för att registrera alla tjänster som krävs av en ramverksfunktion. Till exempel AddOptions registrerar tilläggsmetoden alla tjänster som krävs för att använda alternativ.

Ramverksbaserade tjänster

När du använder något av de tillgängliga värd- eller appbyggarmönstren tillämpas standardvärden och tjänster registreras av ramverket. Överväg några av de mest populära värd- och appbyggarmönstren:

När du har skapat en byggare från någon av dessa API:er IServiceCollection har tjänsterna definierats av ramverket, beroende på hur du konfigurerade värden. För appar baserade på .NET-mallar kan ramverket registrera hundratals tjänster.

I följande tabell visas ett litet urval av dessa ramverksregistrerade tjänster:

Tjänsttyp Livstid
Microsoft.Extensions.DependencyInjection.IServiceScopeFactory Singleton
IHostApplicationLifetime Singleton
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Tillfälligt
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticListener Singleton
System.Diagnostics.DiagnosticSource Singleton

Tjänstlivslängd

Tjänster kan registreras med någon av följande livslängder:

I följande avsnitt beskrivs var och en av de föregående livslängderna. Välj en lämplig livslängd för varje registrerad tjänst.

Tillfälligt

Tillfälliga livslängdstjänster skapas varje gång de begärs från tjänstcontainern. Om du vill registrera en tjänst som tillfällig anropar du AddTransient.

I appar som bearbetar begäranden tas tillfälliga tjänster bort i slutet av begäran. Den här livslängden medför allokeringar per/begäran, eftersom tjänster löses och konstrueras varje gång. Mer information finns i Riktlinjer för beroendeinmatning: IDisposable-vägledning för tillfälliga och delade instanser.

Omfattad

För webbprogram anger en begränsad livslängd att tjänster skapas en gång per klientbegäran (anslutning). Registrera begränsade tjänster med AddScoped.

I appar som bearbetar begäranden tas begränsade tjänster bort i slutet av begäran.

Kommentar

När du använder Entity Framework Core AddDbContext registrerar DbContext tilläggsmetoden typer med en begränsad livslängd som standard.

En begränsad tjänst bör alltid användas inifrån ett omfång – antingen ett implicit omfång (till exempel ASP.NET Cores omfång per begäran) eller ett explicit omfång som skapats med IServiceScopeFactory.CreateScope().

Lös inte en begränsad tjänst direkt från en singleton med konstruktorinmatning eller genom att begära den från IServiceProvider i singletonen. Detta gör att den begränsade tjänsten fungerar som en singleton, vilket kan leda till felaktigt tillstånd när efterföljande begäranden bearbetas.

Det är acceptabelt att anropa en tjänst med begränsat omfång i en singleton om du skapar och använder ett explicit omfång med IServiceScopeFactory.

Det är också bra att:

  • Lös en singleton-tjänst från en begränsad eller tillfällig tjänst.
  • Lös en begränsad tjänst från en annan begränsad eller tillfällig tjänst.

I utvecklingsmiljön genererar som standard en lösning av en tjänst från en annan tjänst med längre livslängd ett undantag. Mer information finns i Omfångsverifiering.

Singleton

Singleton Lifetime-tjänster skapas antingen:

  • Första gången de begärs.
  • Av utvecklaren när du tillhandahåller en implementeringsinstans direkt till containern. Den här metoden behövs sällan.

Varje efterföljande begäran om tjänstimplementeringen från containern för beroendeinmatning använder samma instans. Om appen kräver singleton-beteende tillåter du att tjänstcontainern hanterar tjänstens livslängd. Implementera inte designmönstret för singleton och ange kod för att ta bort singletonen. Tjänster ska aldrig tas bort med kod som löste tjänsten från containern. Om en typ eller fabrik registreras som en singleton tas singletonen bort automatiskt av containern.

Registrera singleton-tjänster med AddSingleton. Singleton-tjänster måste vara trådsäkra och används ofta i tillståndslösa tjänster.

I appar som bearbetar begäranden tas singleton-tjänster bort när de ServiceProvider tas bort vid programavstängning. Eftersom minnet inte släpps förrän appen stängs av bör du överväga minnesanvändning med en singleton-tjänst.

Metoder för tjänstregistrering

Ramverket tillhandahåller metoder för tjänstregistreringstillägg som är användbara i specifika scenarier:

Metod Automatisk
objekt
förfogande
Flera
Implementeringar
Passera args
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()

Exempel:

services.AddSingleton<IMyDep, MyDep>();
Ja Ja Nej
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})

Exempel:

services.AddSingleton<IMyDep>(sp => new MyDep());
services.AddSingleton<IMyDep>(sp => new MyDep(99));
Ja Ja Ja
Add{LIFETIME}<{IMPLEMENTATION}>()

Exempel:

services.AddSingleton<MyDep>();
Ja Nej Nej
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})

Exempel:

services.AddSingleton<IMyDep>(new MyDep());
services.AddSingleton<IMyDep>(new MyDep(99));
Nej Ja Ja
AddSingleton(new {IMPLEMENTATION})

Exempel:

services.AddSingleton(new MyDep());
services.AddSingleton(new MyDep(99));
Nej Nej Ja

Mer information om typhantering finns i avsnittet Avyttring av tjänster .

Registrering av en tjänst med endast en implementeringstyp motsvarar registrering av tjänsten med samma implementerings- och tjänsttyp. Tänk till exempel på följande kod:

services.AddSingleton<ExampleService>();

Detta motsvarar registrering av tjänsten med både tjänsten och implementeringen av samma typer:

services.AddSingleton<ExampleService, ExampleService>();

Den här likvärdigheten är anledningen till att flera implementeringar av en tjänst inte kan registreras med de metoder som inte använder en explicit tjänsttyp. Dessa metoder kan registrera flera instanser av en tjänst, men alla har samma implementering typ.

Någon av metoderna för tjänstregistrering kan användas för att registrera flera tjänstinstanser av samma tjänsttyp. I följande exempel AddSingleton anropas två gånger med IMessageWriter som tjänsttyp. Det andra anropet till AddSingleton åsidosätter det föregående när det matchas som IMessageWriter och läggs till i föregående när flera tjänster matchas via IEnumerable<IMessageWriter>. Tjänsterna visas i den ordning de registrerades när de löstes via IEnumerable<{SERVICE}>.

using ConsoleDI.IEnumerableExample;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
builder.Services.AddSingleton<ExampleService>();

using IHost host = builder.Build();

_ = host.Services.GetService<ExampleService>();

await host.RunAsync();

Föregående exempelkälla registrerar två implementeringar av IMessageWriter.

using System.Diagnostics;

namespace ConsoleDI.IEnumerableExample;

public sealed class ExampleService
{
    public ExampleService(
        IMessageWriter messageWriter,
        IEnumerable<IMessageWriter> messageWriters)
    {
        Trace.Assert(messageWriter is LoggingMessageWriter);

        var dependencyArray = messageWriters.ToArray();
        Trace.Assert(dependencyArray[0] is ConsoleMessageWriter);
        Trace.Assert(dependencyArray[1] is LoggingMessageWriter);
    }
}

Definierar ExampleService två konstruktorparametrar: en enda IMessageWriter, och en IEnumerable<IMessageWriter>. Den enda IMessageWriter är den sista implementeringen som ska registreras, medan den IEnumerable<IMessageWriter> representerar alla registrerade implementeringar.

Ramverket innehåller TryAdd{LIFETIME} också tilläggsmetoder som endast registrerar tjänsten om det inte redan finns en registrerad implementering.

I följande exempel registreras anropet till AddSingleton som en implementering för ConsoleMessageWriter.IMessageWriter Anropet till TryAddSingleton har ingen effekt eftersom IMessageWriter det redan har en registrerad implementering:

services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();

Har TryAddSingleton ingen effekt, eftersom det redan har lagts till och "try" misslyckas. Hävdar ExampleService följande:

public class ExampleService
{
    public ExampleService(
        IMessageWriter messageWriter,
        IEnumerable<IMessageWriter> messageWriters)
    {
        Trace.Assert(messageWriter is ConsoleMessageWriter);
        Trace.Assert(messageWriters.Single() is ConsoleMessageWriter);
    }
}

Mer information finns i:

Metoderna TryAddEnumerable (ServiceDescriptor) registrerar endast tjänsten om det inte redan finns en implementering av samma typ. Flera tjänster löses via IEnumerable<{SERVICE}>. När du registrerar tjänster lägger du till en instans om en av samma typer inte redan har lagts till. Biblioteksförfattare använder TryAddEnumerable för att undvika att registrera flera kopior av en implementering i containern.

I följande exempel registreras TryAddEnumerable det första anropet till MessageWriter som en implementering för IMessageWriter1. Det andra anropet registreras MessageWriter för IMessageWriter2. Det tredje anropet har ingen effekt eftersom IMessageWriter1 det redan har en registrerad implementering av MessageWriter:

public interface IMessageWriter1 { }
public interface IMessageWriter2 { }

public class MessageWriter : IMessageWriter1, IMessageWriter2
{
}

services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter2, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());

Tjänstregistrering är orderoberoende förutom när du registrerar flera implementeringar av samma typ.

IServiceCollection är en samling ServiceDescriptor objekt. I följande exempel visas hur du registrerar en tjänst genom att skapa och lägga till en ServiceDescriptor:

string secretKey = Configuration["SecretKey"];
var descriptor = new ServiceDescriptor(
    typeof(IMessageWriter),
    _ => new DefaultMessageWriter(secretKey),
    ServiceLifetime.Transient);

services.Add(descriptor);

De inbyggda Add{LIFETIME} metoderna använder samma metod. Se till exempel AddScoped-källkoden.

Konstruktorinmatningsbeteende

Tjänster kan lösas med hjälp av:

Konstruktorer kan acceptera argument som inte tillhandahålls av beroendeinmatning, men argumenten måste tilldela standardvärden.

När IServiceProvider eller ActivatorUtilities löser tjänster kräver konstruktorinjektion en offentlig konstruktor.

När ActivatorUtilities hanterar tjänster kräver konstruktorinjektion att det bara finns en tillämplig konstruktor. Konstruktoröverlagring stöds, men det finns bara en överlagring vars argument kan uppfyllas genom beroendeinmatning.

Omfångsverifiering

När appen körs i Development miljön och anropar CreateApplicationBuilder för att skapa värden utför standardtjänstleverantören kontroller för att kontrollera att:

  • Begränsade tjänster matchas inte från rottjänstleverantören.
  • Begränsade tjänster matas inte in i singletons.

Rottjänstprovidern skapas när BuildServiceProvider anropas. Rottjänstleverantörens livslängd motsvarar appens livslängd när providern börjar med appen och tas bort när appen stängs av.

Begränsade tjänster tas bort av containern som skapade dem. Om en begränsad tjänst skapas i rotcontainern höjs tjänstens livslängd effektivt till singleton eftersom den endast tas bort av rotcontainern när appen stängs av. Validering av tjänstomfattningar fångar upp dessa situationer när BuildServiceProvider anropas.

Omfångsscenarier

IServiceScopeFactory är alltid registrerad som en singleton, men IServiceProvider kan variera beroende på livslängden för den innehållande klassen. Om du till exempel löser tjänster från ett omfång och någon av dessa tjänster kräver en IServiceProvider, är det en scopad instans.

Om du vill uppnå omfångstjänster inom implementeringar av IHostedService, till exempel BackgroundService, ska du inte mata in tjänstberoenden via konstruktorinmatning. Mata i stället in IServiceScopeFactory, skapa ett omfång och lös sedan beroenden från omfånget för att använda lämplig tjänstlivslängd.

namespace WorkerScope.Example;

public sealed class Worker(
    ILogger<Worker> logger,
    IServiceScopeFactory serviceScopeFactory)
    : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using (IServiceScope scope = serviceScopeFactory.CreateScope())
            {
                try
                {
                    logger.LogInformation(
                        "Starting scoped work, provider hash: {hash}.",
                        scope.ServiceProvider.GetHashCode());

                    var store = scope.ServiceProvider.GetRequiredService<IObjectStore>();
                    var next = await store.GetNextAsync();
                    logger.LogInformation("{next}", next);

                    var processor = scope.ServiceProvider.GetRequiredService<IObjectProcessor>();
                    await processor.ProcessAsync(next);
                    logger.LogInformation("Processing {name}.", next.Name);

                    var relay = scope.ServiceProvider.GetRequiredService<IObjectRelay>();
                    await relay.RelayAsync(next);
                    logger.LogInformation("Processed results have been relayed.");

                    var marked = await store.MarkAsync(next);
                    logger.LogInformation("Marked as processed: {next}", marked);
                }
                finally
                {
                    logger.LogInformation(
                        "Finished scoped work, provider hash: {hash}.{nl}",
                        scope.ServiceProvider.GetHashCode(), Environment.NewLine);
                }
            }
        }
    }
}

I föregående kod, medan appen körs, är bakgrundstjänsten:

  • Beror på IServiceScopeFactory.
  • Skapar en IServiceScope för att hantera andra tjänster.
  • Löser begränsade tjänster för förbrukning.
  • Fungerar med att bearbeta objekt och sedan vidarebefordra dem och markerar dem slutligen som bearbetade.

I källkodsexemplet kan du se hur implementeringar av kan dra nytta av IHostedService begränsade tjänstlivslängder.

Nyckelade tjänster

Från och med .NET 8 finns det stöd för tjänstregistreringar och sökningar baserat på en nyckel, vilket innebär att det är möjligt att registrera flera tjänster med en annan nyckel och använda den här nyckeln för sökningen.

Tänk till exempel på det fall där du har olika implementeringar av gränssnittet IMessageWriter: MemoryMessageWriter och QueueMessageWriter.

Du kan registrera dessa tjänster med hjälp av överbelastningen av tjänstregistreringsmetoderna (som vi såg tidigare) som stöder en nyckel som en parameter:

services.AddKeyedSingleton<IMessageWriter, MemoryMessageWriter>("memory");
services.AddKeyedSingleton<IMessageWriter, QueueMessageWriter>("queue");

key är inte begränsat till string. key kan vara vilken object som helst, så länge typen implementerar Equals korrekt.

I konstruktorn för klassen som använder IMessageWriterlägger du till FromKeyedServicesAttribute för att ange nyckeln för tjänsten som ska matchas:

public class ExampleService
{
    public ExampleService(
        [FromKeyedServices("queue")] IMessageWriter writer)
    {
        // Omitted for brevity...
    }
}

Se även