How to access a dynamic object's property dynamically (by variable)?

6.3k Views Asked by At

I have a class named Configurationwhich inherits from DynamicObject. It it a Dictionary<string, object>. In the constructor, it reads a text file and loads all the values from it splitted by =, so you can create dynamic configuration objects like this:

dynamic configuration = new Configuration("some_file.ini");

Here is my full class:

public sealed class Configuration : DynamicObject
{
    private string Path { get; set; }
    private Dictionary<string, object> Dictionary { get; set; }

    public Configuration(string fileName)
    {
        this.Path = fileName;
        this.Dictionary = new Dictionary<string, object>();

        this.Populate();
    }

    ~Configuration()
    {
        if (this.Dictionary != null)
        {
            this.Dictionary.Clear();
        }
    }

    private void Populate()
    {
        using (StreamReader reader = new StreamReader(this.Path))
        {
            string line;
            string currentSection = string.Empty;

            while ((line = reader.ReadLine()) != null)
            {
                if (string.IsNullOrWhiteSpace(line))
                {
                    continue;
                }

                if (line.StartsWith("[") && line.EndsWith("]"))
                {
                    currentSection = line.Trim('[', ']');
                }
                else if (line.Contains("="))
                {
                    this.Dictionary.Add(string.Format("{0}{1}{2}",
                        currentSection,
                        (currentSection != string.Empty) ? "_" : string.Empty,
                        line.Split('=')[0].Trim()),
                        ParseValue(line.Split('=')[1].Trim().Split(';')[0]));
                }
            }
        }
    }

    private object ParseValue(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
            return string.Empty;

        if (value.StartsWith("\"") && value.EndsWith("\""))
            return value.Substring(1, value.Length - 1);

        if (IsNumeric(value))
            return Convert.ToInt32(value);

        // TODO: FIXME Floating values (not to be confuse with IPAddress).
        //if (IsFloating(value))
        //    return Convert.ToDouble(value);

        if (string.Compare(value, "true", StringComparison.OrdinalIgnoreCase) == 0)
            return true;

        if (string.Compare(value, "false", StringComparison.OrdinalIgnoreCase) == 0)
            return false;

        return value;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (this.Dictionary.ContainsKey(binder.Name))
        {
            if (this.Dictionary[binder.Name] == null)
            {
                result = null;
            }
            else
            {
                result = this.Dictionary[binder.Name];
            }

            return true;
        }

        throw new ConfigurationException(binder.Name);
    }

    public override string ToString()
    {
        string result = this.Path + " [ ";

        int processed = 0;

        foreach (KeyValuePair<string, object> value in this.Dictionary)
        {
            result += value.Key;
            processed++;

            if (processed < this.Dictionary.Count)
            {
                result += ", ";
            }
        }

        result += " ]";

        return result;
    }

    private static bool IsNumeric(string value)
    {
        foreach (char c in value)
            if (!char.IsDigit(c))
                return false;

        return true;
    }
    private static bool IsFloating(string value)
    {
        foreach (char c in value)
            if (!char.IsDigit(c) && c != '.')
                return false;

        return true;
    }

    private class NullObject { }
}

Everything is working flawlessly. I've overriden TryGetMember and I'm returning the object based on the GetMemberBinder Name property. However, I'm facing a problem.

When I execute the following code:

string s = "some_config_key";

string d = configuration.s;

It retrieves the value of the key s rather than some_config_key, meaning that it doesn't evalute the value of the variable s. Is there a workaround for this?

Thanks.

1

There are 1 best solutions below

1
On

C# dynamic features are partially compiled like the property/method access. To understand this lets take an example.

dynamic myVar = new { a=1, b="test" };
myVar.a += 1;

Here the C# type system would not test for the validity of the property while compilation, only at runtime the code is executed and if the property is not found it will through you runtime error.

So in your code the property access is already complied to access the property named "s" on the dynamic object "configuration".

In C# no-way you can do this not even in dynamically typed languages like python or javascript.

You have to use like this.

dynamic configuration = getConfig("path/to/file");
var myobj = configuration as IDictionary<string,object>;
string s = "config_key";
var value = myobj[s]; // this is correct.