appsettings.json值转枚举失败抛InvalidOperationException的解决及替代方案
解决配置读取枚举时的转换异常及替代方案
一、修复枚举读取异常的方法
当配置值不在枚举定义范围内时,直接用GetValue<SubscriberKind?>会触发转换异常,可通过以下方式避免:
1. 安全转换枚举值
先读取配置的字符串值,再用Enum.TryParse尝试转换,转换失败时自定义处理逻辑:
internal static class ConfiguratorFactory { public static ISubscriptionConfigurator Create(IConfiguration config) { var kindStr = config.GetValue<string>("Subscriber:Kind"); if (!Enum.TryParse<SubscriberKind>(kindStr, out var subscriberKind)) { throw new ArgumentException($"无效的订阅类型: {kindStr}", nameof(kindStr)); // 也可返回默认配置器,或记录日志后抛出更友好的异常 } return subscriberKind switch { SubscriberKind.UserTrades => new UserTrades.FtxConfigurator(config), _ => throw new ArgumentOutOfRangeException(nameof(subscriberKind)) }; } }
2. 添加默认枚举项
在枚举中新增Unknown项作为转换失败的默认值,避免抛出异常:
public enum SubscriberKind { Unknown, UserTrades }
转换时使用带默认值的重载:
var kindStr = config.GetValue<string>("Subscriber:Kind"); Enum.TryParse<SubscriberKind>(kindStr, true, out var subscriberKind); return subscriberKind switch { SubscriberKind.UserTrades => new UserTrades.FtxConfigurator(config), SubscriberKind.Unknown => throw new ArgumentException($"无效的订阅类型: {kindStr}", nameof(kindStr)), _ => throw new ArgumentOutOfRangeException(nameof(subscriberKind)) };
二、无需枚举的替代实现方案
若想彻底避免枚举的限制(如新增类型需修改枚举定义),可采用以下方案:
1. 直接基于字符串匹配
跳过枚举,直接用配置中的字符串值匹配对应的配置器,扩展性更强:
internal static class ConfiguratorFactory { public static ISubscriptionConfigurator Create(IConfiguration config) { var exchange = config.GetValue<string>("Subscriber:Exchange"); var kindStr = config.GetValue<string>("Subscriber:Kind"); return (exchange, kindStr) switch { ("ftx", "UserTrades") => new UserTrades.FtxConfigurator(config), ("ftx", "DropCopy") => new DropCopy.FtxConfigurator(config), // 新增订阅类型时,直接添加对应的分支即可 _ => throw new ArgumentException($"不支持的订阅组合: Exchange={exchange}, Kind={kindStr}") }; } }
2. 策略模式+依赖注入
通过特性标记配置器支持的Exchange和Kind,结合DI容器自动发现配置器,实现完全解耦的扩展:
步骤1:定义特性
[AttributeUsage(AttributeTargets.Class)] public class SubscriberConfiguratorAttribute : Attribute { public string Exchange { get; } public string Kind { get; } public SubscriberConfiguratorAttribute(string exchange, string kind) { Exchange = exchange; Kind = kind; } }
步骤2:标记配置器类
[SubscriberConfigurator("ftx", "UserTrades")] public class FtxUserTradesConfigurator : ISubscriptionConfigurator { public FtxUserTradesConfigurator(IConfiguration config) { // 初始化逻辑 } } [SubscriberConfigurator("ftx", "DropCopy")] public class FtxDropCopyConfigurator : ISubscriptionConfigurator { public FtxDropCopyConfigurator(IConfiguration config) { // 初始化逻辑 } }
步骤3:实现工厂类
internal class ConfiguratorFactory { private readonly IEnumerable<ISubscriptionConfigurator> _configurators; public ConfiguratorFactory(IEnumerable<ISubscriptionConfigurator> configurators) { _configurators = configurators; } public ISubscriptionConfigurator Create(IConfiguration config) { var exchange = config.GetValue<string>("Subscriber:Exchange"); var kindStr = config.GetValue<string>("Subscriber:Kind"); var configurator = _configurators.FirstOrDefault(c => { var attr = c.GetType().GetCustomAttribute<SubscriberConfiguratorAttribute>(); return attr != null && attr.Exchange == exchange && attr.Kind == kindStr; }); if (configurator == null) { throw new ArgumentException($"不支持的订阅组合: Exchange={exchange}, Kind={kindStr}"); } return configurator; } }
这种方式下,新增订阅类型只需添加对应的配置器类并标记特性,无需修改工厂代码,符合开闭原则。
内容的提问来源于stack exchange,提问作者nop
相关产品推荐
相关产品推荐

