views:

52

answers:

1

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); }
}
+1  A: 

Unless you have other purposes for the default value attribute, I would avoid the entire reflection approach, and instead use an extension class that performs value conversion and where you can supply the default values. You could bundle up the type conversion directly into the extension method to avoid having to do explicit calls to 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;
    }
}

Now your configuration class could be:

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

This approach allows you to supply default values to types that don't have convenient compile time representations (like DateTime, TimeSpan, Point, etc). It also avoid all of the messy reflection logic.

If you need to access the default values via the base class, then you may want to use the reflection/attribute approach. But even the, you could simply initialize your value dictionary with defaults by iterating over all public properties and accessing their getter to fetch the default value at creation time.

LBushkin
I hadn't encountered Convert.ChangeType() before. Thanks for pointing it out; it will be very helpful. The rest of the suggestion is useful as well.
Lawrence Johnston