如何从父类容器中获取配置子类的不同原始类型值?
配置元素类型提取解决方案
我想创建一组类来存储配置文件中的各种原始类型配置值(布尔、字符串、整数等)。虽然知道这有点重复造轮子,但这些类除了读取配置文件外,还集成了ILogger日志功能,并且在未加载配置文件时支持通过用户输入获取值。
目前遇到的问题是:将这些对象存储在ConfigElement类型的字典中后,无法直接提取各自的原始类型值。我清楚用同一个方法返回不同类型会破坏多态性,希望找到可行的替代方案。
现有代码示例
abstract class ConfigElement { // 所有ConfigElement共享的成员 public static ILogger logger; public static bool useConfig; protected string _key; protected string _commandPrompt; protected ConfigElement(string key, string commandPrompt) { _key = key; _commandPrompt = commandPrompt; } abstract public void SetValue(string value); public static ConfigElement Create(string key, string commandPrompt, string inValue) { return new ConfigStringElement(key, commandPrompt, inValue); } public static ConfigElement Create(string key, string commandPrompt, bool inValue) { return new ConfigBoolElement(key, commandPrompt, inValue); } } class ConfigStringElement : ConfigElement { public string Value; public ConfigStringElement(string key, string commandPrompt, string inValue = "") : base(key, commandPrompt) { this.Value = inValue; } override public void SetValue(string input) { Value = input; } public string GetValue() { if (!ConfigElement.useConfig) { Console.WriteLine(_commandPrompt); string userInput = Console.ReadLine(); Value = userInput; } logger.Information($"Config Element: {_key} value: {Value}."); return Value; } override public string GetConfigString() { return Value; } } class ConfigBoolElement : ConfigElement { public bool Value; public ConfigBoolElement(string key, string commandPrompt, bool inValue = false) : base(key, commandPrompt) { Value = inValue; } override public void SetValue(string input) { List<string> trueEntries = new List<string> { "yes", "y", "true", "t" }; Value = trueEntries.Contains(input.ToLower()); } public bool GetValue() { if (!ConfigElement.useConfig) { Value = GetTrueOrFalse(_commandPrompt); } logger.Information($"Config Element: {_key} value: {Value}."); return Value; } }
可行解决方案
方案1:泛型抽象基类
将基类改为泛型类型,让每个子类对应具体的原始类型,既保留多态性,又能强类型获取值:
public interface IConfigElement { string Key { get; } string CommandPrompt { get; } void SetValue(string value); string GetConfigString(); } public abstract class ConfigElement<T> : IConfigElement { public static ILogger Logger; public static bool UseConfig; public string Key { get; } public string CommandPrompt { get; } public T Value { get; protected set; } protected ConfigElement(string key, string commandPrompt, T defaultValue) { Key = key; CommandPrompt = commandPrompt; Value = defaultValue; } public abstract void SetValue(string value); public abstract string GetConfigString(); public T GetValue() { if (!UseConfig) { Value = GetUserInput(); } Logger.Information($"Config Element: {Key} value: {Value}."); return Value; } protected abstract T GetUserInput(); } public class ConfigStringElement : ConfigElement<string> { public ConfigStringElement(string key, string commandPrompt, string defaultValue = "") : base(key, commandPrompt, defaultValue) { } public override void SetValue(string input) => Value = input; public override string GetConfigString() => Value; protected override string GetUserInput() { Console.WriteLine(CommandPrompt); return Console.ReadLine(); } } public class ConfigBoolElement : ConfigElement<bool> { private static readonly List<string> TrueValues = new List<string> { "yes", "y", "true", "t" }; public ConfigBoolElement(string key, string commandPrompt, bool defaultValue = false) : base(key, commandPrompt, defaultValue) { } public override void SetValue(string input) => Value = TrueValues.Contains(input.ToLower()); public override string GetConfigString() => Value.ToString(); protected override bool GetUserInput() { while (true) { Console.WriteLine(CommandPrompt); var input = Console.ReadLine()?.ToLower(); if (TrueValues.Contains(input)) return true; if (new List<string> { "no", "n", "false", "f" }.Contains(input)) return false; Console.WriteLine("无效输入,请输入yes/no/y/n/true/false/t/f"); } } }
使用方式:
字典可以存储IConfigElement接口,获取值时强转为对应泛型类:
var configDict = new Dictionary<string, IConfigElement> { {"UserName", new ConfigStringElement("UserName", "请输入用户名:")}, {"IsEnabled", new ConfigBoolElement("IsEnabled", "是否启用功能?(yes/no):")} }; var userName = ((ConfigElement<string>)configDict["UserName"]).GetValue(); var isEnabled = ((ConfigElement<bool>)configDict["IsEnabled"]).GetValue();
方案2:添加泛型TryGetValue方法
在基类中定义泛型的TryGetValue抽象方法,子类实现类型匹配逻辑,避免强制转换:
abstract class ConfigElement { // 原有成员... public abstract bool TryGetValue<T>(out T value); } class ConfigStringElement : ConfigElement { // 原有成员... public override bool TryGetValue<T>(out T value) { if (typeof(T) == typeof(string)) { value = (T)(object)Value; return true; } value = default; return false; } } class ConfigBoolElement : ConfigElement { // 原有成员... public override bool TryGetValue<T>(out T value) { if (typeof(T) == typeof(bool)) { value = (T)(object)Value; return true; } value = default; return false; } }
使用方式:
if (configDict["UserName"].TryGetValue(out string userName)) { // 使用userName } if (configDict["IsEnabled"].TryGetValue(out bool isEnabled)) { // 使用isEnabled }
方案3:访问者模式
通过访问者模式,让不同类型的配置元素接受访问者处理,适合需要对多种类型执行复杂操作的场景:
public interface IConfigElementVisitor { void Visit(ConfigStringElement element); void Visit(ConfigBoolElement element); // 新增类型时需要添加对应的Visit方法 } abstract class ConfigElement { // 原有成员... public abstract void Accept(IConfigElementVisitor visitor); } class ConfigStringElement : ConfigElement { // 原有成员... public override void Accept(IConfigElementVisitor visitor) => visitor.Visit(this); } class ConfigBoolElement : ConfigElement { // 原有成员... public override void Accept(IConfigElementVisitor visitor) => visitor.Visit(this); }
创建访问者获取值:
public class ConfigValueGetter<T> : IConfigElementVisitor { public T Value { get; private set; } public bool Success { get; private set; } public void Visit(ConfigStringElement element) { if (typeof(T) == typeof(string)) { Value = (T)(object)element.GetValue(); Success = true; } } public void Visit(ConfigBoolElement element) { if (typeof(T) == typeof(bool)) { Value = (T)(object)element.GetValue(); Success = true; } } }
使用方式:
var userNameGetter = new ConfigValueGetter<string>(); configDict["UserName"].Accept(userNameGetter); if (userNameGetter.Success) { var userName = userNameGetter.Value; }
方案对比
- 泛型基类:类型安全,代码结构清晰,适合明确知道配置类型的场景,但需要显式转换。
- TryGetValue方法:灵活性高,无需提前知道类型,适合动态场景,但要处理转换失败的情况。
- 访问者模式:扩展性好,适合需要对不同类型配置执行多种操作的场景,但新增配置类型时需修改访问者接口。
内容的提问来源于stack exchange,提问作者Jordan Larson
相关产品推荐
相关产品推荐

