1

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 (DictionaryEntry 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?

Arne Lund
  • 2,366
  • 3
  • 26
  • 39
  • oh; sorry, Arne, i hadn't noticed you had updated your main question, i misunderstood your question to me below. i think i would word it differently; but if i understand your question, then i would say yes, val => val is the way to do a "pass-through" of this type, where you need the result of the lambda to be the same as the parameter. :) – shelleybutterfly Jul 13 '11 at 23:22

3 Answers3

5

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)));
        ...
    }
}
Vlad
  • 35,022
  • 6
  • 77
  • 199
  • The parameter to the `Convert` method should probably be `object` instead of `string`, but apart from this, +1 (or the value should be cast to string before calling `Convert`). – vgru Jul 12 '11 at 21:28
  • @Groo: the OP casts the `Value` to string, so I assume it's a string. I've corrected the code to include the cast. (EDIT: took your edited suggestion.) – Vlad Jul 12 '11 at 21:32
  • @Vlad: although this works really well, I am not so sure if we really need one additional class per the type we convert from. – Arne Lund Jul 13 '11 at 20:31
  • @Arne: added an alternate method – Vlad Jul 13 '11 at 22:39
3

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);
shelleybutterfly
  • 3,216
  • 15
  • 32
  • 1
    you could just say `new ConfigSection(int.Parse)` instead of `new ConfigSection(val => int.Parse(val))` – Vlad Jul 12 '11 at 21:35
  • @shelleybutterfly: this is almost what I need. What is the simplest lambda expression to do nothing? Is there anything simpler than val => val – Arne Lund Jul 13 '11 at 20:33
  • well if you really mean to do nothing at see: http://stackoverflow.com/questions/1743013/is-there-a-way-to-specify-an-empty-c-lambda-expression which suggests `() => { }` which would give the equivalent of an empty void function. :) if there's anything you need to change it to what you need instead of just almost let me know and i'll try to figure it out. :) – shelleybutterfly Jul 13 '11 at 22:04
2

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'.
Dan Bryant
  • 27,329
  • 4
  • 56
  • 102
  • vow, this is a lot of useful ideas. Yet using reflection seems to be an overkill, I'd rather specify the conversions myself, especially for possibly locale-specific DateTime values. Thanks! – Arne Lund Jul 13 '11 at 20:37
  • 1
    @Arne, IConvertible is a good choice if you're working primarily with primitive types, as you can use that as a generic constraint and then call the various Convert static methods to get the types you want. Reflection would definitely be a mechanism of last resort, but is sometimes handy if you need to integrate with classes that you don't control. – Dan Bryant Jul 13 '11 at 21:23
  • @Dan I liked your suggestion to use IConvertible – Arne Lund Jul 14 '11 at 14:06