我有一个包含其他类的配置值的字典(将定期执行各种专用逻辑的任务),它们保存在数据库中,然后在执行时传回。
我想为这个Dictionary创建一个强类型的包装器,以便轻松访问这些值并将它们转换为正确的类型。
目前我有这样的事情:
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.
}
我的问题是,是否有办法改进这种设计。
我考虑过的一件事是使用反射来获取属性的名称(以及配置值),如所讨论的here。这节省了必须创建字符串键并隐式强制键与属性具有相同的名称(这是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); }
}
答案 0 :(得分: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等)提供默认值。它还避免了所有混乱的反射逻辑。
如果需要通过基类访问默认值,则可能需要使用反射/属性方法。但即便如此,您也可以通过迭代所有公共属性并访问其getter以在创建时获取默认值,使用默认值初始化您的值字典。