0

--EDIT-- Rewritten to better clarify what I am trying to do, and what the problem is. Apologies that it is not succinct, but the context may help with the understanding.

I have a program where I run a number of simulations, against various scenarios. I store the results of these simulations and scenarios in a class ("Results class"), with the basic results stored as properties of the class, and the various scenarios and sensitivities stored in dictionaries within the class.

Because different users will want different outputs from the simulations, I am trying to develop a means of allowing users to generate custom reports. To do this, I am using reflection. I use reflection to work through each Property in the Results class, and add it to a treeview, using the same hierarchy as the Report Class. The user can then browse the treeview, see each property, and select the ones that they want in their report.

When the user selects a property to report, I use the tree hierarchy to turn this into a "path" - a delimited string that describes the property branch to the item of interest: eg:

Results.TotalCapacity; or:
Results.UsableCapacity; or
Results.Scenarios[HighCase].TotalCapacity; or
Results.Sensitivity.ModifiedCapacity.

Ultimately, I want to use reflection to parse these paths, and retrieve the values from them.

I borrowed code heavily from this link, but am struggling with the best way to retrieve the appropriate object when one of the objects in the path is a dictionary.

I have managed to get this to work, but I'd appreciate any feedback as to how it can be improved or made more robust. Casting the dictionary to a list and then grabbing the key via an index is clearly not optimum. I can modify my 'path' code to return the dictionary key, but not sure how to get the dictionary value from the key using reflection.

Code as follows:

public object GetPropertyValueFromPath(object baseObject, string path)
{
    //Split into the base path elements
    string[] pp = CorrectPathForDictionaries(path).Split(new[]{ '.' }, StringSplitOptions.RemoveEmptyEntries);

    //Set initial value of the ValueObject
    object valueObject = baseObject;            
    foreach (var prop in pp)
    {
        if (prop.Contains("["))
        {
            //Will be a dictionary.  Get the name of the dictionary, and the index element of interest:
            string dictionary = prop.Substring(0, prop.IndexOf("["));
            int index = Convert.ToInt32(prop.Substring(prop.IndexOf("[") + 1, prop.Length - prop.IndexOf("]")));

            //Get the property info for the dictionary
            PropertyInfo dictInfo = valueObject.GetType().GetProperty(dictionary);
            if (dictInfo != null)
            {
                //Get the dictionary from the PropertyInformation
                valueObject = dictInfo.GetValue(valueObject, null);
                //Convert it to a list to provide easy access to the item of interest.  The List<> will be a set of key-value pairs.
                List<object> values = ((IEnumerable)valueObject).Cast<object>().ToList();
                //Use "GetValue" with the "value" parameter and the index to get the list object we want.
                valueObject = values[index].GetType().GetProperty("Value").GetValue(values[index], null); 
            }
        }
        else
        {
            PropertyInfo propInfo = valueObject.GetType().GetProperty(prop);
            if (propInfo != null)
            {
                valueObject = propInfo.GetValue(valueObject, null);
            }
        }
    }
    return valueObject;
}
Community
  • 1
  • 1
ainwood
  • 992
  • 7
  • 17
  • by teh way: "CorrectPathForDictionaries" simply takes the path and corrects it such that Scenario.[3].ModifiedCapacity is changed to 'Scenario[3].ModifiedCapacity. – ainwood Jul 18 '16 at 03:16
  • What have you tried? Please include a good [mcve] showing clearly what you've tried, with a precise description of what _specific_ problem you're having. Note that relevant member for reflection on a dictionary for which you don't know the type parameters at compile time (if you do, then of course you can just cast the object to the right dictionary type) is the `Item` property. – Peter Duniho Jul 18 '16 at 06:45

2 Answers2

0

OK... not the prettiest code in the world, but it works (any suggestions to improve are appreciated).

I test to see if I am attempting to get a value from a dictionary. If so, I get the name of the dictionary, and the index of the element requested. I use GetProperty on the Dictionary Name to get a PropertyInfo object, and then use GetValue on that PropertyInfo to get the actual dictionary. As a way to handle mucking around with the key-value pairs, I cast the dictionary to a list, and then us the index to get a single key-value pair. I then call GetProperty("Value") to get the desired object from the dictionary.

public object GetPropertyValueFromPath(object baseObject, string path)
    {
        //Split into the base path elements
        string[] pp = CorrectPathForDictionaries(path).Split(new[]{ '.' }, StringSplitOptions.RemoveEmptyEntries);

        //Set initial value of the ValueObject
        object valueObject = baseObject;            
        foreach (var prop in pp)
        {
            if (prop.Contains("["))
            {
                //Will be a dictionary.  Get the name of the dictionary, and the index element of interest:
                string dictionary = prop.Substring(0, prop.IndexOf("["));
                int index = Convert.ToInt32(prop.Substring(prop.IndexOf("[") + 1, prop.Length - prop.IndexOf("]")));

                //Get the property info for the dictionary
                PropertyInfo dictInfo = valueObject.GetType().GetProperty(dictionary);
                if (dictInfo != null)
                {
                    //Get the dictionary from the PropertyInformation
                    valueObject = dictInfo.GetValue(valueObject, null);
                    //Convert it to a list to provide easy access to the item of interest.  The List<> will be a set of key-value pairs.
                    List<object> values = ((IEnumerable)valueObject).Cast<object>().ToList();
                    //Use "GetValue" with the "value" parameter and the index to get the list object we want.
                    valueObject = values[index].GetType().GetProperty("Value").GetValue(values[index], null); 
                }
            }
            else
            {
                PropertyInfo propInfo = valueObject.GetType().GetProperty(prop);
                if (propInfo != null)
                {
                    valueObject = propInfo.GetValue(valueObject, null);
                }
            }
        }
        return valueObject;
    }
ainwood
  • 992
  • 7
  • 17
  • Dictionaries are unordered, so retrieving elements by index is guaranteed to do the wrong thing. If it works at all, it's purely coincidence. – Peter Duniho Jul 18 '16 at 06:46
  • They're loaded into a treeview, and retrieved using the same code, so will be in the same order, but I get your point. – ainwood Jul 18 '16 at 20:10
0

Lacking a good Minimal, Complete, and Verifiable example that shows clearly what you've tried, along with a clear explanation of what specific difficulty you're having, it's not very clear to me what you're asking here. However, it seems like maybe you are having trouble retrieving the value for a given key from a dictionary object for which you don't know the type at compile time (since if you did know the type at compile time, presumably you'd just cast to that type and then use the dictionary directly).

If that's correct, then this should help:

There are two ways to accomplish this. One is by directly accessing the object via reflection, using the Item property, which is the actual compiled name of the indexer property of a class. That would look something like this:

private static string GetValue(object dictionary, object key)
{
    PropertyInfo itemProperty = dictionary.GetType().GetProperty("Item");

    return itemProperty.GetValue(dictionary, new object[] { key }).ToString();
}

It's straight-forward reflection: get the PropertyInfo object, and then pass the key value to the GetValue() method to get the value.

This works fine, but it's a bit clumsy and requires going through reflection to inspect the type every time you want to get the value. You could improve on the latter by caching the PropertyInfo object, but it would still be clumsy.

You can instead let the runtime do the work of caching while at the same time allowing the code to read much more closely to what it would look like for a normal dictionary look-up, by using the dynamic type:

private static string GetValue(dynamic dictionary, dynamic key)
{
    return dictionary[key].ToString();
}

Note that both the dictionary object and the key need to be dynamic. When you write the method this way, at runtime a "binder" will be created based on the types of the dynamic variables and the code expression in which they are used. Essentially, the final compilation of the code expression is deferred until runtime when the types are known, so that it can be executed normally. (If you were to execute the same statement with different types, the runtime will create different binders for each combination of types.)

Either of these address the specific issue of getting the value for a given key when the types aren't known at compile time. If this does not address your question, please improve the question by providing a good MCVE as requested, along with a precise description of what that code does and what you want it to do instead.

Community
  • 1
  • 1
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136