0

I have a Dictionary containing configuration values for other classes (tasks which will be executed periodically performing assorted specialized logic) which are persisted in a database and then passed back in at execution time.

I want to create a strongly typed wrapper for this Dictionary, both to allow easy access to the values and to cast them to the proper type.

At the moment I have something like this:

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.
}

My question is if there's a way to improve this design.

One thing I've considered is using reflection to get the name for the property (and hence the configuration value) as discussed here. This saves having to create a string key and implicitly forces the key to have the same name as the property (which is required for the InitializeDefaultValues() code to function), but it also obscures the fact that this is the case and that that the name of the configuration value will change if the name of the property is changed. So it's a trade off.

This would look something like the following:

// 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); }
}
4

1 に答える 1

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など)を持たない型にデフォルト値を提供できます。また、厄介なリフレクションロジックをすべて回避します。

基本クラスを介してデフォルト値にアクセスする必要がある場合は、リフレクション/属性アプローチを使用することをお勧めします。しかし、それでも、すべてのパブリックプロパティを反復処理し、作成時にデフォルト値をフェッチするためにそれらのゲッターにアクセスすることで、デフォルトで値ディクショナリを初期化することができます。

于 2010-07-06T17:02:52.367 に答える