开发者

How to refactor highly repetitive reading sections from config files

I need to convert multiple sections of config file to dictionaries. Values of those dictionaries have different types. The following two classes work, but they are almost identical:

public class IntConfigSection
{
    private static readonly ILog Log = LogManager.GetLogger(typeof(IntConfigSection));
    public static Dictionary<String, int> LoadSection(string sectionName)
    {
        var ret = new Dictionary<String, int>();
        try
        {
            var offsetsHash = (Hashtable)ConfigurationManager.GetSection(sectionName);
            foreach (DictionaryEntry entry in offsetsHash)
            {
                ret.Add((String)entry.Key, int.Parse((String)entry.Value));
            }
        }
        catch(Exception e)
        {
            Log.ErrorFormat("LoadSection:" + e);
        }
        return ret;
    }
}

public class StringConfigSection
{
    private static readonly ILog Log = LogManager.GetLogger(typeof(StringConfigSection));
    public static Dictionary<String, String> LoadSection(string sectionName)
    {
        var ret = new Dictionary<String, String>();
        try
        {
            var offsetsHash = (Hashtable)ConfigurationManager.GetSection(sectionName);
            foreach (DictionaryEntry entry in offsetsHash)
            {
                ret.Add((String)entry.Key, (String)entry.Value);
            }
        }
        catch (Exception e)
        {
            Log.ErrorFormat("LoadSection:" + e);
        }
        return ret;
    }
}

The following code does not work as required, but it demonstrates what I am trying to accomplish:

public class ConfigSection<T>
{
    private static readonly ILog Log = LogManager.GetLogger(typeof(StringConfigSection));
    public static Dictionary<String, T> LoadSection(string sectionName)
    {
        var ret = new Dictionary<String, T>();
        try
        {
            var offsetsHash = (Hashtable)ConfigurationManager.GetSection(sectionName);
            foreach (DictionaryEntry entry in offsetsHash)
            {
                //builds but does not always do what I want
                ret.Add((String)entry.Key, (T)entry.Value);
                // does not compile
                //ret.Add((String)entry.Key, T.Parse((String)entry.Value));
            }
        }
        catch (Exception e)
        {
            Log.ErrorFormat("LoadSection:" + e);
        }
        return ret;
    }
}

Edit: my final version looks as follows:

public class ConfigSectionLoader
{
    public static Dictionary<String, int> LoadIntSection(string sectionName)
    {
        return ConfigSection<int>.LoadSection(sectionName, int.Parse);
    }

    public static Dictionary<String, String> LoadStringSection(string sectionName)
    {
        return ConfigSection<String>.LoadSection(sectionName, val => val);
    }
}

internal class ConfigSection<T>
{
    private static readonly ILog Log = LogManager.GetLogger(typeof(StringConfigSection));
    internal static Dictionary<String, T> LoadSection(string sectionName, Func<String, T> parseFunc)
    {
        var ret = new Dictionary<String, T>();
        try
        {
            var hash = (Hashtable)ConfigurationManager.GetSection(sectionName);
            foreach (Dict开发者_如何学运维ionaryEntry entry in hash)
            {
                ret.Add((String)entry.Key, parseFunc((String)entry.Value));
            }
        }
        catch (Exception e)
        {
            Log.ErrorFormat("LoadSection:" + e);
        }
        return ret;
    }
}

My only concern is this: is val => val the simplest lambda that does nothing?


I would propose the following:

public abstract class ConfigSectionBase<T>
{
    public static Dictionary<String, T> LoadSection(string sectionName)
    {
        ...
        //builds but does not always do what I want
        ret.Add((String)entry.Key, Convert((string)entry.Value));
        ...
    }
    abstract T Convert(string v);
}

public class IntConfigSection: ConfigSectionBase<int>
{
    override int Convert(string v)
    {
        return int.Parse(v);
    }
}

public class StringConfigSection: ConfigSectionBase<string>
{
    override string Convert(string v)
    {
        return v;
    }
}

(Disclaimer: I didn't try the code!)

EDIT:
It should be possible to avoid specifying the Convert function for each type individually by using Convert.ChangeType:

public abstract class ConfigSection<T>
{
    public static Dictionary<String, T> LoadSection(string sectionName)
    {
        ...
        //builds but does not always do what I want
        ret.Add((String)entry.Key,
                (T)Convert.ChangeType((string)entry.Value, typeof(T)));
        ...
    }
}


you could just pass in a function that actually does the parsing for that type: (note: untested code, consider this an idea rather than working code :D)

public class ConfigSection<T>
{
    private static readonly ILog Log = LogManager.GetLogger(typeof(StringConfigSection));
    public static Dictionary<String, T> LoadSection(string sectionName, Func<String,T> parseFunc)
    {
        var ret = new Dictionary<String, T>();
        try
        {
            var offsetsHash = (Hashtable)ConfigurationManager.GetSection(sectionName);
            foreach (DictionaryEntry entry in offsetsHash)
            {
                ret.Add((String)entry.Key, parseFunc((String)entry.Value));
            }
        }
        catch (Exception e)
        {
            Log.ErrorFormat("LoadSection:" + e);
        }
        return ret;
    }
}

and then pass in your actual parser as a lambda (or similar..)

or you could actually include the parser in a constructor for the class and then have it be a member so you don't have to pass it in every time.

public class ConfigSection<T>
{
    private Func<String, T> myParseFunc = null;

    public ConfigSection<T>(Func<String,T> parParseFunc)
    {
       myParseFunc = parParseFunc;
    }

    private static readonly ILog Log = LogManager.GetLogger(typeof(StringConfigSection));
    public static Dictionary<String, T> LoadSection(string sectionName)
    {
        var ret = new Dictionary<String, T>();
        try
        {
            var offsetsHash = (Hashtable)ConfigurationManager.GetSection(sectionName);
            foreach (DictionaryEntry entry in offsetsHash)
            {
                ret.Add((String)entry.Key, myParseFunc((String)entry.Value));
            }
        }
        catch (Exception e)
        {
            Log.ErrorFormat("LoadSection:" + e);
        }
        return ret;
    }
}

and you could call it like so:

ConfigSection<int> = new ConfigSection<int>(int.Parse);


This is a common problem and you always end up with some degree of duplication. The trick is figuring out how small you can make the duplication and where you want to localize it. In this particular example, you could:

  1. Pass a Func<string, T> parseMethod parameter into your generic class constructor. This allows you to support any type and even allows the same type to be parsed differently for different config section instances.
  2. Get a TypeConverter for the type and try to Convert from string.
  3. Require that the type support IConvertible and convert to/from string.
  4. Use reflection to find a static Parse or TryParse method on the type. This is not ideal, but the BCL has examples of using similar hacky 'magic method names'.
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜