Skip to content
Go back

.NET Keyed Services:优雅解决同接口多实现的注册与选择

在.NET开发中,我们经常会遇到这样的场景:同一个接口有多个不同的实现,而我们需要在运行时根据具体情况选择使用哪一个。传统的解决方案往往涉及复杂的工厂模式或大量的条件判断逻辑。而.NET 8引入的Keyed Services特性为这个常见问题提供了优雅的解决方案。

问题场景:多实现选择的困境

假设你正在构建一个文件存储服务,需要支持两种存储方式:

  1. 本地磁盘存储:适用于开发环境或小规模应用
  2. 云存储服务:适用于生产环境或需要高可用性的场景

这两种实现都遵循相同的接口IStorageService,但在不同的业务场景下,我们需要使用不同的实现。传统的做法可能是这样:

// 定义统一的存储接口
public interface IStorageService
{
    Task<string> SaveFileAsync(Stream fileStream, string fileName);
    Task<Stream> GetFileAsync(string fileName);
    Task DeleteFileAsync(string fileName);
}

// 本地存储实现
public class LocalStorageService : IStorageService
{
    private readonly string _basePath;

    public LocalStorageService(IConfiguration configuration)
    {
        _basePath = configuration["Storage:LocalPath"] ?? "./uploads";
    }

    public async Task<string> SaveFileAsync(Stream fileStream, string fileName)
    {
        var fullPath = Path.Combine(_basePath, fileName);
        Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);

        using var fileWrite = File.Create(fullPath);
        await fileStream.CopyToAsync(fileWrite);

        return fullPath;
    }

    public async Task<Stream> GetFileAsync(string fileName)
    {
        var fullPath = Path.Combine(_basePath, fileName);
        return File.OpenRead(fullPath);
    }

    public async Task DeleteFileAsync(string fileName)
    {
        var fullPath = Path.Combine(_basePath, fileName);
        if (File.Exists(fullPath))
        {
            File.Delete(fullPath);
        }
    }
}

// 云存储实现
public class CloudStorageService : IStorageService
{
    private readonly string _connectionString;
    private readonly string _containerName;

    public CloudStorageService(IConfiguration configuration)
    {
        _connectionString = configuration["Storage:CloudConnectionString"]!;
        _containerName = configuration["Storage:ContainerName"] ?? "files";
    }

    public async Task<string> SaveFileAsync(Stream fileStream, string fileName)
    {
        // Azure Blob Storage 或 AWS S3 实现
        // 这里简化示例
        var blobClient = new BlobClient(_connectionString, _containerName, fileName);
        await blobClient.UploadAsync(fileStream, overwrite: true);

        return blobClient.Uri.ToString();
    }

    public async Task<Stream> GetFileAsync(string fileName)
    {
        var blobClient = new BlobClient(_connectionString, _containerName, fileName);
        var response = await blobClient.DownloadAsync();
        return response.Value.Content;
    }

    public async Task DeleteFileAsync(string fileName)
    {
        var blobClient = new BlobClient(_connectionString, _containerName, fileName);
        await blobClient.DeleteIfExistsAsync();
    }
}

传统解决方案的局限性

在没有Keyed Services之前,我们通常需要采用以下几种方式之一:

方案一:使用工厂模式

public interface IStorageServiceFactory
{
    IStorageService GetStorageService(string storageType);
}

public class StorageServiceFactory : IStorageServiceFactory
{
    private readonly IServiceProvider _serviceProvider;

    public StorageServiceFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IStorageService GetStorageService(string storageType)
    {
        return storageType.ToLower() switch
        {
            "local" => _serviceProvider.GetService<LocalStorageService>()!,
            "cloud" => _serviceProvider.GetService<CloudStorageService>()!,
            _ => throw new ArgumentException($"Unknown storage type: {storageType}")
        };
    }
}

// 注册服务
services.AddScoped<LocalStorageService>();
services.AddScoped<CloudStorageService>();
services.AddScoped<IStorageServiceFactory, StorageServiceFactory>();

// 使用
public class DocumentService
{
    private readonly IStorageServiceFactory _storageFactory;

    public DocumentService(IStorageServiceFactory storageFactory)
    {
        _storageFactory = storageFactory;
    }

    public async Task SaveDocumentAsync(Stream content, string fileName, bool useCloud)
    {
        var storageService = _storageFactory.GetStorageService(useCloud ? "cloud" : "local");
        await storageService.SaveFileAsync(content, fileName);
    }
}

这种方式的问题在于:

方案二:注入IEnumerable并手动筛选

// 注册所有实现
services.AddScoped<IStorageService, LocalStorageService>();
services.AddScoped<IStorageService, CloudStorageService>();

// 使用时手动筛选
public class DocumentService
{
    private readonly IEnumerable<IStorageService> _storageServices;

    public DocumentService(IEnumerable<IStorageService> storageServices)
    {
        _storageServices = storageServices;
    }

    public async Task SaveDocumentAsync(Stream content, string fileName, bool useCloud)
    {
        // 这种方式需要额外的类型检查或标记接口
        var storageService = useCloud
            ? _storageServices.OfType<CloudStorageService>().First()
            : _storageServices.OfType<LocalStorageService>().First();

        await storageService.SaveFileAsync(content, fileName);
    }
}

这种方式的缺点:

Keyed Services:优雅的解决方案

.NET 8引入的Keyed Services特性允许我们在注册服务时为每个实现指定一个键(key),然后在需要时通过键来获取特定的实现。这种方式简洁、类型安全,且不需要额外的工厂代码。

基础使用方法

使用Keyed Services只需要三个简单步骤:

步骤1:使用键注册服务

// Program.cs 或 Startup.cs
var builder = WebApplication.CreateBuilder(args);

// 使用键注册同一接口的不同实现
builder.Services.AddKeyedScoped<IStorageService, LocalStorageService>("local");
builder.Services.AddKeyedScoped<IStorageService, CloudStorageService>("cloud");

步骤2:通过FromKeyedServices特性注入

public class DocumentService
{
    private readonly IStorageService _localStorage;
    private readonly IStorageService _cloudStorage;

    public DocumentService(
        [FromKeyedServices("local")] IStorageService localStorage,
        [FromKeyedServices("cloud")] IStorageService cloudStorage)
    {
        _localStorage = localStorage;
        _cloudStorage = cloudStorage;
    }

    public async Task SaveDocumentAsync(Stream content, string fileName, bool useCloud)
    {
        var storageService = useCloud ? _cloudStorage : _localStorage;
        await storageService.SaveFileAsync(content, fileName);
    }
}

步骤3:在需要时选择使用

现在你可以根据业务逻辑灵活选择使用哪个实现,而不需要任何工厂代码或条件判断。

在控制器中使用Keyed Services

在ASP.NET Core的控制器中,我们也可以轻松使用Keyed Services:

[ApiController]
[Route("api/[controller]")]
public class FilesController : ControllerBase
{
    private readonly IStorageService _localStorage;
    private readonly IStorageService _cloudStorage;

    public FilesController(
        [FromKeyedServices("local")] IStorageService localStorage,
        [FromKeyedServices("cloud")] IStorageService cloudStorage)
    {
        _localStorage = localStorage;
        _cloudStorage = cloudStorage;
    }

    [HttpPost("upload")]
    public async Task<IActionResult> UploadFile(
        IFormFile file,
        [FromQuery] string storageType = "local")
    {
        var storageService = storageType.ToLower() switch
        {
            "cloud" => _cloudStorage,
            _ => _localStorage
        };

        using var stream = file.OpenReadStream();
        var path = await storageService.SaveFileAsync(stream, file.FileName);

        return Ok(new { FilePath = path, StorageType = storageType });
    }

    [HttpGet("{fileName}")]
    public async Task<IActionResult> DownloadFile(
        string fileName,
        [FromQuery] string storageType = "local")
    {
        var storageService = storageType.ToLower() switch
        {
            "cloud" => _cloudStorage,
            _ => _localStorage
        };

        var stream = await storageService.GetFileAsync(fileName);
        return File(stream, "application/octet-stream", fileName);
    }
}

动态解析Keyed Services

除了通过构造函数注入,我们还可以使用IServiceProvider动态解析带键的服务:

public class DynamicStorageService
{
    private readonly IServiceProvider _serviceProvider;

    public DynamicStorageService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task<string> SaveFileAsync(
        Stream fileStream,
        string fileName,
        string storageKey)
    {
        // 动态获取指定键的服务
        var storageService = _serviceProvider.GetRequiredKeyedService<IStorageService>(storageKey);
        return await storageService.SaveFileAsync(fileStream, fileName);
    }

    public IStorageService GetStorageService(string key)
    {
        // 使用GetKeyedService可以处理键不存在的情况
        return _serviceProvider.GetKeyedService<IStorageService>(key)
            ?? throw new InvalidOperationException($"No storage service registered for key: {key}");
    }
}

实际应用场景

场景一:多支付网关集成

在电商系统中,通常需要集成多个支付网关以支持不同的支付方式:

public interface IPaymentGateway
{
    Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request);
    Task<RefundResult> RefundAsync(string transactionId, decimal amount);
}

public class StripePaymentGateway : IPaymentGateway
{
    private readonly StripeClient _stripeClient;

    public StripePaymentGateway(IConfiguration configuration)
    {
        _stripeClient = new StripeClient(configuration["Stripe:ApiKey"]);
    }

    public async Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request)
    {
        // Stripe特定的支付处理逻辑
        var paymentIntent = await _stripeClient.CreatePaymentIntentAsync(
            request.Amount,
            request.Currency,
            request.PaymentMethodId);

        return new PaymentResult
        {
            Success = paymentIntent.Status == "succeeded",
            TransactionId = paymentIntent.Id
        };
    }

    public async Task<RefundResult> RefundAsync(string transactionId, decimal amount)
    {
        var refund = await _stripeClient.CreateRefundAsync(transactionId, amount);
        return new RefundResult { Success = refund.Status == "succeeded" };
    }
}

public class PayPalPaymentGateway : IPaymentGateway
{
    private readonly PayPalClient _paypalClient;

    public PayPalPaymentGateway(IConfiguration configuration)
    {
        _paypalClient = new PayPalClient(
            configuration["PayPal:ClientId"],
            configuration["PayPal:Secret"]);
    }

    public async Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request)
    {
        // PayPal特定的支付处理逻辑
        var order = await _paypalClient.CreateOrderAsync(request);
        await _paypalClient.CaptureOrderAsync(order.Id);

        return new PaymentResult
        {
            Success = true,
            TransactionId = order.Id
        };
    }

    public async Task<RefundResult> RefundAsync(string transactionId, decimal amount)
    {
        var refund = await _paypalClient.RefundCaptureAsync(transactionId, amount);
        return new RefundResult { Success = refund.Status == "COMPLETED" };
    }
}

// 注册服务
builder.Services.AddKeyedScoped<IPaymentGateway, StripePaymentGateway>("stripe");
builder.Services.AddKeyedScoped<IPaymentGateway, PayPalPaymentGateway>("paypal");

// 订单处理服务
public class OrderService
{
    private readonly IServiceProvider _serviceProvider;

    public OrderService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task<PaymentResult> ProcessOrderAsync(Order order)
    {
        // 根据用户选择的支付方式获取对应的支付网关
        var paymentGateway = _serviceProvider.GetRequiredKeyedService<IPaymentGateway>(
            order.PaymentMethod.ToLower());

        var paymentRequest = new PaymentRequest
        {
            Amount = order.TotalAmount,
            Currency = order.Currency,
            PaymentMethodId = order.PaymentMethodId
        };

        return await paymentGateway.ProcessPaymentAsync(paymentRequest);
    }
}

场景二:多数据库支持

当应用需要支持多种数据库时,Keyed Services可以让切换变得简单:

public interface IDatabaseProvider
{
    Task<IEnumerable<T>> QueryAsync<T>(string sql, object parameters);
    Task<int> ExecuteAsync(string sql, object parameters);
}

public class SqlServerProvider : IDatabaseProvider
{
    private readonly string _connectionString;

    public SqlServerProvider(IConfiguration configuration)
    {
        _connectionString = configuration.GetConnectionString("SqlServer")!;
    }

    public async Task<IEnumerable<T>> QueryAsync<T>(string sql, object parameters)
    {
        using var connection = new SqlConnection(_connectionString);
        return await connection.QueryAsync<T>(sql, parameters);
    }

    public async Task<int> ExecuteAsync(string sql, object parameters)
    {
        using var connection = new SqlConnection(_connectionString);
        return await connection.ExecuteAsync(sql, parameters);
    }
}

public class PostgreSqlProvider : IDatabaseProvider
{
    private readonly string _connectionString;

    public PostgreSqlProvider(IConfiguration configuration)
    {
        _connectionString = configuration.GetConnectionString("PostgreSql")!;
    }

    public async Task<IEnumerable<T>> QueryAsync<T>(string sql, object parameters)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        return await connection.QueryAsync<T>(sql, parameters);
    }

    public async Task<int> ExecuteAsync(string sql, object parameters)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        return await connection.ExecuteAsync(sql, parameters);
    }
}

// 注册
builder.Services.AddKeyedScoped<IDatabaseProvider, SqlServerProvider>("sqlserver");
builder.Services.AddKeyedScoped<IDatabaseProvider, PostgreSqlProvider>("postgresql");

// 使用配置决定默认数据库
var defaultDb = builder.Configuration["Database:Provider"] ?? "sqlserver";
builder.Services.AddScoped<IDatabaseProvider>(sp =>
    sp.GetRequiredKeyedService<IDatabaseProvider>(defaultDb));

场景三:多租户架构

在多租户SaaS应用中,不同租户可能需要不同的服务实现:

public interface ITenantService
{
    Task<TenantConfiguration> GetConfigurationAsync(string tenantId);
    Task<bool> ValidateTenantAsync(string tenantId);
}

public class PremiumTenantService : ITenantService
{
    public async Task<TenantConfiguration> GetConfigurationAsync(string tenantId)
    {
        // 高级租户的配置:更多功能、更高限制
        return new TenantConfiguration
        {
            MaxUsers = 1000,
            MaxStorage = 1000 * 1024 * 1024 * 1024L, // 1TB
            Features = new[] { "advanced-analytics", "custom-branding", "api-access" }
        };
    }

    public async Task<bool> ValidateTenantAsync(string tenantId)
    {
        // 高级租户验证逻辑
        return true;
    }
}

public class StandardTenantService : ITenantService
{
    public async Task<TenantConfiguration> GetConfigurationAsync(string tenantId)
    {
        // 标准租户的配置:基础功能
        return new TenantConfiguration
        {
            MaxUsers = 50,
            MaxStorage = 10 * 1024 * 1024 * 1024L, // 10GB
            Features = new[] { "basic-features" }
        };
    }

    public async Task<bool> ValidateTenantAsync(string tenantId)
    {
        // 标准租户验证逻辑
        return true;
    }
}

// 注册
builder.Services.AddKeyedScoped<ITenantService, PremiumTenantService>("premium");
builder.Services.AddKeyedScoped<ITenantService, StandardTenantService>("standard");

// 中间件中根据租户类型注入对应服务
public class TenantMiddleware
{
    private readonly RequestDelegate _next;

    public TenantMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context, IServiceProvider serviceProvider)
    {
        var tenantId = context.Request.Headers["X-Tenant-Id"].FirstOrDefault();
        var tenantTier = await DetermineTenantTierAsync(tenantId);

        var tenantService = serviceProvider.GetRequiredKeyedService<ITenantService>(tenantTier);
        context.Items["TenantService"] = tenantService;

        await _next(context);
    }

    private async Task<string> DetermineTenantTierAsync(string? tenantId)
    {
        // 从数据库或缓存中获取租户级别
        return "standard"; // 简化示例
    }
}

Keyed Services的进阶特性

使用AnyKey获取所有实现

有时我们需要获取某个接口的所有已注册实现。Keyed Services提供了特殊的KeyedService.AnyKey来实现这一需求:

public class NotificationService
{
    private readonly IEnumerable<INotificationProvider> _allProviders;

    public NotificationService(
        [FromKeyedServices(KeyedService.AnyKey)] IEnumerable<INotificationProvider> allProviders)
    {
        _allProviders = allProviders;
    }

    public async Task SendToAllChannelsAsync(string message)
    {
        var tasks = _allProviders.Select(provider =>
            provider.SendAsync(message));

        await Task.WhenAll(tasks);
    }
}

组合使用命名和键控服务

在复杂场景中,我们可以结合使用默认注册和键控注册:

// 注册默认实现(用于最常见的场景)
builder.Services.AddScoped<IStorageService, LocalStorageService>();

// 同时注册键控实现(用于特殊场景)
builder.Services.AddKeyedScoped<IStorageService, LocalStorageService>("local");
builder.Services.AddKeyedScoped<IStorageService, CloudStorageService>("cloud");
builder.Services.AddKeyedScoped<IStorageService, BackupStorageService>("backup");

// 使用时可以灵活选择
public class DocumentManager
{
    private readonly IStorageService _defaultStorage;
    private readonly IStorageService _backupStorage;

    public DocumentManager(
        IStorageService defaultStorage, // 注入默认实现
        [FromKeyedServices("backup")] IStorageService backupStorage) // 注入特定键的实现
    {
        _defaultStorage = defaultStorage;
        _backupStorage = backupStorage;
    }

    public async Task SaveWithBackupAsync(Stream content, string fileName)
    {
        // 保存到默认存储
        await _defaultStorage.SaveFileAsync(content, fileName);

        // 同时备份
        content.Position = 0; // 重置流位置
        await _backupStorage.SaveFileAsync(content, $"backup/{fileName}");
    }
}

生命周期管理

Keyed Services支持所有标准的服务生命周期:

// Transient:每次请求都创建新实例
builder.Services.AddKeyedTransient<ICache, MemoryCache>("memory");

// Scoped:每个请求作用域内共享实例
builder.Services.AddKeyedScoped<IStorageService, LocalStorageService>("local");

// Singleton:整个应用程序生命周期内单例
builder.Services.AddKeyedSingleton<ILogger, FileLogger>("file");

最佳实践与注意事项

键的命名约定

为了保持代码的可维护性,建议遵循以下键命名约定:

// 使用常量定义键,避免魔法字符串
public static class StorageKeys
{
    public const string Local = "local";
    public const string Cloud = "cloud";
    public const string Backup = "backup";
}

// 注册时使用常量
builder.Services.AddKeyedScoped<IStorageService, LocalStorageService>(StorageKeys.Local);
builder.Services.AddKeyedScoped<IStorageService, CloudStorageService>(StorageKeys.Cloud);

// 使用时也使用常量
public class DocumentService
{
    public DocumentService(
        [FromKeyedServices(StorageKeys.Local)] IStorageService localStorage,
        [FromKeyedServices(StorageKeys.Cloud)] IStorageService cloudStorage)
    {
        // ...
    }
}

错误处理

当请求的键不存在时,GetRequiredKeyedService会抛出异常。在某些场景下,使用GetKeyedService并处理null值可能更合适:

public class ResilientStorageService
{
    private readonly IServiceProvider _serviceProvider;

    public ResilientStorageService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task<string> SaveFileAsync(Stream fileStream, string fileName, string preferredStorage)
    {
        // 尝试使用首选存储
        var storageService = _serviceProvider.GetKeyedService<IStorageService>(preferredStorage);

        if (storageService == null)
        {
            // 回退到默认存储
            storageService = _serviceProvider.GetKeyedService<IStorageService>(StorageKeys.Local)
                ?? throw new InvalidOperationException("No storage service available");
        }

        try
        {
            return await storageService.SaveFileAsync(fileStream, fileName);
        }
        catch (Exception ex)
        {
            // 如果首选存储失败,尝试备用存储
            var backupService = _serviceProvider.GetKeyedService<IStorageService>(StorageKeys.Backup);
            if (backupService != null && backupService != storageService)
            {
                fileStream.Position = 0;
                return await backupService.SaveFileAsync(fileStream, fileName);
            }

            throw;
        }
    }
}

性能考虑

Keyed Services的解析性能与普通服务解析相当,不会带来显著的性能开销。但是,如果在热路径上频繁解析服务,考虑将解析结果缓存:

public class CachedStorageResolver
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ConcurrentDictionary<string, IStorageService> _cache = new();

    public CachedStorageResolver(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IStorageService GetStorageService(string key)
    {
        return _cache.GetOrAdd(key, k =>
            _serviceProvider.GetRequiredKeyedService<IStorageService>(k));
    }
}

测试支持

在单元测试中,Keyed Services同样易于使用:

[Test]
public async Task SaveDocument_UsingCloudStorage_ShouldSucceed()
{
    // Arrange
    var services = new ServiceCollection();
    var mockCloudStorage = new Mock<IStorageService>();
    mockCloudStorage
        .Setup(s => s.SaveFileAsync(It.IsAny<Stream>(), It.IsAny<string>()))
        .ReturnsAsync("https://cloud.storage/file.txt");

    services.AddKeyedScoped(StorageKeys.Cloud, (sp, key) => mockCloudStorage.Object);

    var serviceProvider = services.BuildServiceProvider();

    var documentService = new DocumentService(
        null!,
        serviceProvider.GetRequiredKeyedService<IStorageService>(StorageKeys.Cloud));

    // Act
    using var stream = new MemoryStream();
    var result = await documentService.SaveDocumentAsync(stream, "test.txt", true);

    // Assert
    Assert.That(result, Is.EqualTo("https://cloud.storage/file.txt"));
    mockCloudStorage.Verify(s => s.SaveFileAsync(It.IsAny<Stream>(), "test.txt"), Times.Once);
}

与其他模式的对比

Keyed Services vs 工厂模式

工厂模式:

Keyed Services:

选择建议: 对于简单的多实现选择场景,优先使用Keyed Services。如果需要复杂的创建逻辑或参数化创建,考虑工厂模式。

Keyed Services vs 策略模式

策略模式:

Keyed Services:

选择建议: Keyed Services可以作为策略模式的实现基础,两者并不互斥。在需要复杂选择逻辑时,可以使用Keyed Services注册策略,用策略管理器选择。

迁移现有代码

如果你的项目中已经使用了工厂模式或其他方式来处理多实现问题,迁移到Keyed Services是相对简单的:

步骤1:识别工厂方法

找出所有用于选择服务实现的工厂方法:

// 旧代码
public class StorageServiceFactory : IStorageServiceFactory
{
    public IStorageService GetStorageService(string type)
    {
        return type switch
        {
            "local" => _serviceProvider.GetService<LocalStorageService>(),
            "cloud" => _serviceProvider.GetService<CloudStorageService>(),
            _ => throw new ArgumentException()
        };
    }
}

步骤2:更新服务注册

将原来的注册方式改为使用键注册:

// 移除工厂注册
// services.AddScoped<IStorageServiceFactory, StorageServiceFactory>();

// 添加键控服务注册
services.AddKeyedScoped<IStorageService, LocalStorageService>("local");
services.AddKeyedScoped<IStorageService, CloudStorageService>("cloud");

步骤3:更新使用代码

将工厂调用改为使用FromKeyedServicesGetKeyedService

// 旧代码
public class DocumentService
{
    private readonly IStorageServiceFactory _factory;

    public DocumentService(IStorageServiceFactory factory)
    {
        _factory = factory;
    }

    public async Task SaveAsync(Stream content, string fileName, string storageType)
    {
        var storage = _factory.GetStorageService(storageType);
        await storage.SaveFileAsync(content, fileName);
    }
}

// 新代码
public class DocumentService
{
    private readonly IServiceProvider _serviceProvider;

    public DocumentService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task SaveAsync(Stream content, string fileName, string storageType)
    {
        var storage = _serviceProvider.GetRequiredKeyedService<IStorageService>(storageType);
        await storage.SaveFileAsync(content, fileName);
    }
}

步骤4:逐步迁移

不需要一次性迁移所有代码。可以先保留工厂模式,在工厂内部使用Keyed Services:

public class StorageServiceFactory : IStorageServiceFactory
{
    private readonly IServiceProvider _serviceProvider;

    public StorageServiceFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IStorageService GetStorageService(string type)
    {
        // 内部使用Keyed Services
        return _serviceProvider.GetRequiredKeyedService<IStorageService>(type);
    }
}

这样可以在保持向后兼容的同时,逐步将各个模块迁移到直接使用Keyed Services。

总结

Keyed Services是.NET 8中一个强大而优雅的特性,它为处理同一接口的多个实现提供了框架级的解决方案。相比传统的工厂模式或手动筛选方式,Keyed Services具有以下优势:

核心优势:

适用场景:

使用建议:

Keyed Services不是万能的,但它确实解决了一个非常常见的问题。当你发现自己在编写工厂代码或大量if-else来选择服务实现时,考虑一下Keyed Services是否能让你的代码更加简洁优雅。

掌握Keyed Services,让你的.NET应用架构更加灵活而不失简洁。


标签


Previous Post
.NET 中抽象的真实代价:何时使用,何时避免
Next Post
在 ASP.NET Core 中构建基于权限的授权体系:从角色到细粒度访问控制