tags:

views:

110

answers:

2

The values in a file are read as string and can be double, string or int or maybe even lists. An example file:

DatabaseName=SomeBase
Classes=11;12;13
IntValue=3        //this is required!
DoubleValue=4.0

I was thinking something like this:

class ConfigValues
{
    private static SomeObject _utiObject;
    private static string _cfgFileName = "\\SomeSettings.cfg";
    private static Dictionary<string, Type> _settingNamesAndTypes = 
         new Dictionary<string, Type>();
    private static Dictionary<string, object> _settings = new Dictionary<string, object>();
    private static string _directory = string.Empty;
    const string _impossibleDefaultValue = "987ABC654DEF321GHI";

    public static T GetConfigValue<T>(string cfgName)
    {
        object value;
        if (_settings.TryGetValue(cfgName, out value))
            return (T)value;
        else
            return default(T);
    }

    public static bool LoadConfig(Dictionary<string, Type> reqSettings, 
          Dictionary<string, Type> optSettings,
          Dictionary<string, object> optDefaultValues, out string errorMsg)
    {
        errorMsg = string.Empty;

        try
        {
            _utiObject = new SomeObject(new string[] { "-c", CfgFileNameAndPath });
        }
        catch (Exception e)
        {
            errorMsg = string.Format("Unable to read {0}. Exception: {1}", 
              CfgFileNameAndPath, e.Message);
            return false;
        }

        foreach (KeyValuePair<string, Type> kVPair in reqSettings)
        {
            if (!ReadCheckAndStore(kVPair, null, out errorMsg))
                return false;

            _settingNamesAndTypes.Add(kVPair.Key, kVPair.Value);

        }
        foreach (KeyValuePair<string, Type> kVPair in optSettings)
        {
            if (!ReadCheckAndStore(kVPair, optDefaultValues[kVPair.Key], out errorMsg))
                return false;

            _settingNamesAndTypes.Add(kVPair.Key, kVPair.Value);
        }
        return true;
    }

    private static bool ReadCheckAndStore(KeyValuePair<string, Type> kVPair, object defaultValue, out string errorMsg)
    {
        errorMsg = string.Empty;
        string usedDefaultValue, value = string.Empty;

        /* required setting */
        if (defaultValue == null)
            usedDefaultValue = _impossibleDefaultValue;
        else
            usedDefaultValue = defaultValue.ToString();

        //all string parameters below
        _utiObject.GetConfigValue(kVPair.Key, usedDefaultValue, ref value);
        if (_impossibleDefaultValue == value)
        {
            errorMsg = string.Format("Required configuration setting {0} was not" +
               "found in {1}", kVPair.Key, CfgFileNameAndPath);
            return false;
        }
        Type type = kVPair.Value;

        _settings[kVPair.Key] = Convert.ChangeType(value, type);

        return true;
    }
}

PS. Additional issue is default values for optional settings. It's not elegant to pass them to LoadConfig in separate Dictionary, but that is an other issue...

+3  A: 

The only way I can think of doing this is to have Dictionary<String,Object> and then cast the Object to the appropriate type.

Your underlying problem is how to dynamically specify the type: http://stackoverflow.com/questions/2466278/dynamically-specify-the-type-in-c

Turns out that type casting (actually unboxing) in C# has very little overhead: http://stackoverflow.com/questions/2466629/c-performance-analysis-how-to-count-cpu-cycles


Update:
Here is how you do the casting:

Dictionary<String,Object> parameters = new Dictionary<String,Object>();

// cast a string
parameters.Add("DatabaseName", "SomeBase");

// cast a list
parameters.Add("Classes", new List<int> { int.Parse("11"), int.Parse("12"), int.Parse("13") });

// cast an integer
parameters.Add("IntValue", int.Parse("3"));

// cast a double
parameters.Add("DoubleValue", Double.Parse("4.0"));

Then when you want to use the values you just do the unboxing:

int intValue = (int)parameters["IntValue"];
Double doubleValue = (Double)parameters["DoubleValue"];
List<int> classes = (List<int>)parameters["Classes"];
// etc...

As I mentioned before: after doing performance testing I found that unboxing has negligent overhead so you should not see any noticeable performance issues.


Update 2.0:

I'm guessing you want to automatically convert the type without having to explicitly specify it when you're adding it into the _settings dictionary. It should work if your dictionary's value is an Object:

Type t = typeof(double);
Object val = Convert.ChangeType("2.0", t);
// you still need to unbox the value to use it
double actual = (double)val;

So your example should work:

_settings[kVPair.Key] = Convert.ChangeType(value, type);

You just need to unbox the value with the correct type when you're going to use it.

Lirik
exactly what i was thinking:)
Veer
sorry. I was not able to use arrowheads and my example was total BS. now squarebrackets
matti
@matti formatting issues? I didn't notice any problems. Anyway, I hope that my answer makes sense... let me know if you want me to clarify something.
Lirik
@lirik: and you please check the new code in my edited question. don't know how to cast the string read from file to type...
matti
I have KeyValuePair<string, Type> kVPair and I want to cast string value to kVPair.Value. How to do this?
matti
@matti see my update... the value should be of type Object so you can cast nearly every imaginable type to it. Just add the value to the dictionary just like I show in my code.
Lirik
thanks a lot! really appreciate. think I took 1 up vote from you. this site has exceptional talent but can't understand how it works. How it's possible that I click an up-arrow after 15mins or so after the previous time it takes the up-vote away. u must edit your answer so I can give u up-vote again. crazy!!!
matti
actually threre is still the problem. that I cannot cast explicitly because I got the type in dictionary (in reqSettings and optSettings).
matti
If you click on the up arrow once it will up-vote, if you click on it again it will remove the up-vote (note clicking down arrow directly down-votes). Can you provide a simple example showing why you can't cast explicitly?
Lirik
I have a Dictionary of <string, Type> (orig. example reqSettings). string tells the setting name and Type it's type. When I add to Dictionary<String,Object> parameters that u suggested I want to just cast the string read from file according to that Type in the reqSettings.
matti
now the example there. u can check how can I implement both ReadCheckAndStore and generic GetConfigValue
matti
once again it is useless since arrowhead generics are removed. I hate this implementation but love people helping!!
matti
thanks but sorry. maybe this is not possible then. but even that is not what I want... I want to unbox the value with using the type in Value of the _settingNamesAndTypes. How come it's not possible?? The 'Type' is not very useful if cannot used in type conversion!
matti
I have a value of certain type and I know it's type. How can I not cast the object to that type in general way?
matti
@lirik: anyway thanks a lot! actually my version of generic GetConfigValue works. I do not have to use these 'double' and other words! think you justmissed what I wanted.
matti
@matti I understood your question, but I think you're getting confused somewhere: in _settingsNamesAndTypes there is nothing to unbox, you're only storing the name and the type of the value, not the actual value. Your actual value is stored in the _settings dictionary, and you can unbox it when you're using it. You have to explicitly cast the value when you're going to use it and there is simply no logical way to avoid that... the "double and other words" were just examples to show you how these ideas work.
Lirik
@lirik: ?? "_settingsNamesAndTypes there is nothing to unbox, you're only storing the name and the type of the value, not the actual value. Your actual value is stored in the _settings dictionary, and you can unbox it when you're using it." --> yes I know! I store Object to _settings in ReadCheckAndStore and get it in GetConfigValue. The List<> does not work if I do not change ReadCheckAndStore... Maybe in some comment I confused... Thanks a lot. and my generic version is working with doubles and stuff. with List<int> and Lis<string> I have to make adjustments to
matti
@matti OK, no problem :)... didn't mean to make it more complicated, just trying to help :).
Lirik
A: 

You don't have to do this with generics.

You could simply have three methods like

public string GetStringConfig(string key){}
public List<int> GetListConfig(string key){}
public int GetIntegerConfig(string key){}

(you would have to throw an error if you try to parse / convert a string that does not match)

The above is like using a SQLAdapter.

This does of course assume you know the type of object the key should return

Tim Jarvis
that is true. but it limits the types. what if someone wants to use a date? then this class has to be changed.
matti
I had made the assumption that you had a finite list of types a user could have as values. You are right a new method would have to be added if you wanted to accept dates. Wouldn't you still have to write some sort of check / conversion method for the string object to be converted to the desired type anyway?
Tim Jarvis
If you *did* go this way, convention is to use the BCL type name, so `GetInt32Config`, not `GetIntegerConfig`.
Marc Gravell
@tim: you're right. this whole thing was not so good idea :(
matti