Лучший способ создать строго типизированную оболочку для словаря<string, string>
У меня есть словарь, содержащий значения конфигурации для других классов (задачи, которые будут выполняться периодически, выполняя различную специализированную логику), которые сохраняются в базе данных, а затем передаются обратно во время выполнения.
Я хочу создать строго типизированную оболочку для этого словаря, чтобы обеспечить легкий доступ к значениям и привести их к нужному типу.
На данный момент у меня есть что-то вроде этого:
public class ConfigurationWrapper {
Dictionary<string, string> _configuration;
public ConfigurationWrapper(Dictionary<string, string> configuration) {
_configuration = configuration;
InitializeDefaultValues();
}
public virtual GetConfigurationCopy() {
return new Dictionary(_configuration);
}
protected InitializeDefaultValues() {
Type t = GetType();
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(t);
foreach (PropertyDescriptor property in properties) {
AttributeCollection attributes = property.Attributes;
DefaultValueAttribute defaultValue = (DefaultValueAttribute)attributes[typeof(DefaultValueAttribute)];
if (defaultValue != null) {
if (!Configuration.ContainsKey(property.Name)) {
Configuration[property.Name] = Convert.ToString(defaultValue.Value, CultureInfo.InvariantCulture);
}
}
}
}
}
public class MyTaskConfigurationWrapper : ConfigurationWrapper {
private const string MyIntPropertyKey = "MyIntProperty";
[DefaultValue(7)]
int MyIntProperty {
get { return Convert.ToInt32(_configuration[MyIntPropertyKey], CultureInfo.InvarientCulture); }
set { _configuration[MyIntPropertyKey] = value.ToString(CultureInfo.InvarientCulture); }
}
// More properties of various types.
}
У меня вопрос, есть ли способ улучшить этот дизайн.
Одна вещь, которую я рассмотрел, - это использование отражения, чтобы получить имя для свойства (и, следовательно, значение конфигурации), как обсуждалось здесь. Это избавляет от необходимости создавать строковый ключ и неявно заставляет ключ иметь то же имя, что и свойство (которое требуется для InitializeDefaultValues()
код функции), но это также скрывает тот факт, что это так и что имя значения конфигурации изменится, если имя свойства будет изменено. Так что это компромисс.
Это будет выглядеть примерно так:
// Could alternately use PropertyHelper example with some compile time checking
protected string GetProperty(MethodBase getMethod) {
if (!getMethod.Name.StartsWith("get_") {
throw new ArgumentException(
"GetProperty must be called from a property");
}
return _configuration[getMethod.Name.Substring(4)];
}
protected string SetProperty(MethodBase getMethod, string value) {
// Similar to above except set instead of get
}
[DefaultValue(7)]
int MyIntProperty {
get { return Convert.ToInt32(GetProperty(MethodInfo.GetCurrentMethod(), CultureInfo.InvarientCulture); }
set { SetProperty(MethodInfo.GetCurrentMethod(), value.ToString(CultureInfo.InvarientCulture); }
}
1 ответ
Если у вас нет других целей для атрибута значения по умолчанию, я бы отказался от всего подхода к отражению и вместо этого использовал бы класс расширения, который выполняет преобразование значений и где вы можете предоставить значения по умолчанию. Вы можете связать преобразование типов непосредственно в метод расширения, чтобы избежать необходимости делать явные вызовы Convert.ToXXX()
,
public static class DictionaryExt
{
// bundles up safe dictionary lookup with value conviersion...
// could be split apart for improved maintenance if you like...
public static TResult ValueOrDefault<TKey,TValue,TResult>(
this DIctionary<TKey,TValue> dictionary, TKey key, TResult defaultVal )
{
TValue value;
return dictionary.TryGetValue( key, out value )
? Convert.ChangeType( value, typeof(TResult) )
: defaultVal;
}
}
Теперь ваш класс конфигурации может быть:
public class MyTaskConfigurationWrapper : ConfigurationWrapper
{
private const string MyIntPropertyKey = "MyIntProperty";
int MyIntProperty
{
// supply the default as a parameter, not reflection attribute
get {
return _config.ValueOrDefault( MyIntPropertyKey, 7 ); }
set {
_config[MyIntPropertyKey] = value.ToString(CultureInfo.InvarientCulture); }
}
// More properties of various types.
}
Этот подход позволяет вам предоставлять значения по умолчанию для типов, которые не имеют удобного представления времени компиляции (например, DateTime, TimeSpan, Point и т. Д.). Это также позволяет избежать всей грязной логики отражения.
Если вам нужен доступ к значениям по умолчанию через базовый класс, то вы можете использовать подход отражения / атрибута. Но даже вы можете просто инициализировать ваш словарь значений по умолчанию, перебирая все открытые свойства и получая доступ к их получателю для получения значения по умолчанию во время создания.