getting a IDictionary item value setter through reflection

279 Views Asked by At

I'm trying to get the setter function of a dictionary item value. I know the object is a Dictionary< TKey,TValue>, but I don't know the types of Tkey and TValue, so I think my only resort is to use an IDictionary.

In pseudo code, I want to do something like this;

Action<object> keySetter = dictionary.items[index].value.setter
Action<object> valueSetter = dictionary.items[index].key.setter

Unfortunately the IDictionary has no indexer and I'm not sure how to get to the actual keys and values.

Right now I'm looping through the dictionary entries and get the setter from that, but whenever I call the setter it doesn't seem to change to value in the dictionary. So I suspect the DictionaryEntry is a copy and doesn't point to the actual value in the dictionary.

//for simplicity sake a Dictionary is added here, but usually the TKey and Tvalue are not known
IDictionary target = new Dictionary<int, string>();
target.Add( 0, "item 1" );

foreach ( DictionaryEntry dictionaryEntry in target )
{
    //want to get the setter for the item's key setter and value setter
    PropertyInfo keyProperty = dictionaryEntry.GetType().GetProperty( "Key" );
    PropertyInfo valueProperty = dictionaryEntry.GetType().GetProperty( "Value" );

    Action<object> keySetter = ( val ) =>
    {
        keyProperty.SetMethod.Invoke( dictionaryEntry, new object[] { val } );
    };

    Action<object> valueSetter = ( val ) =>
    {
        valueProperty.SetMethod.Invoke( dictionaryEntry, new object[] { val } );
    };

    keySetter.Invoke( 1 );
    valueSetter.Invoke( "item 1 value succesfully modified" );

    Console.WriteLine( target.Keys ); //no change
    Console.WriteLine( target.Values ); //no change
}

Since I do know that the IDictionary is actually a Dictionary< TKey, TValue> underneath, maybe I can do some reflection magic to get the setter that way?

1

There are 1 best solutions below

1
On

When you enumerate entries of a Dictionary, Key and Value are copied from Dictionary's internal structures (Entry[]) to new instances of KeyValuePair or DictionaryEntry. Therefore trying to modify these DictionaryEntry is futile, because these changes are not propagated back to the dictionary. To modify Dictionary, you have to use it's indexer or Add, Remove or similar methods.

C# indexers are just a syntactic sugar to use the Dictionary<TKey,TValue>.Item property. So when using reflection, you have to use this property instead.

To create value setter for each item in a Dictionary, you need to get a key for each of these items and then use it as an index argument when setting new value to the Dictionary using it's Item property. Creating a key setter is more difficult, because Dictionary doesn't support changing of existing key. What you have to do is actually remove existing item from the Dictionary and insert a new one with the new key:

// Dictionary.Item property we will use to get/set values
var itemProp = target.GetType().GetProperty("Item");

// Since you want to modify dictionary while enumerating it's items (which is not allowed),
// you have to use .Cast<object>().ToList() to temporarily store all items in the list
foreach (var item in (target as IEnumerable).Cast<object>().ToList())
{
    // item is of type KeyValuePair<TKey,TValue> and has Key and Value properties.
    // Use reflection to read content from the Key property
    var itemKey = item.GetType().GetProperty("Key").GetValue(item);

    Action<Object> valueSetter = (val) => itemProp.SetValue(target, val, new object[] { itemKey });

    // valueSetter(someNewValue);

    // It's not possible to just change the key of some item. As a workaround,
    // you have to remove original item from the dictionary and insert a new item
    // with the original value and with a new key.
    Action<Object> keySetter = (key) =>
    {
        // read value from the dictionary for original key
        var val = itemProp.GetValue(target, new object[] { itemKey });

        // set this value to the disctionary with the new key
        itemProp.SetValue(target, val, new object[] { key });

        // remove original key from the Dictionary
        target.GetType().GetMethod("Remove").Invoke(target, new Object[] { itemKey });

        // need to "remember" the new key to allow the keySetter to be called repeatedly
        itemKey = key;
    };

    // keySetter(someNewKey);
}