首页 > 解决方案 > 通过反射获取 IDictionary 项值设置器

问题描述

我正在尝试获取字典项值的 setter 函数。我知道对象是 Dictionary<TKey,TValue>,但我不知道 Tkey 和 TValue 的类型,所以我认为我唯一的办法是使用 IDictionary。

在伪代码中,我想做这样的事情;

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

不幸的是,IDictionary 没有索引器,我不确定如何获取实际的键和值。

现在我正在遍历字典条目并从中获取设置器,但是每当我调用设置器时,它似乎并没有改变为字典中的值。所以我怀疑 DictionaryEntry 是一个副本,并不指向字典中的实际值。

//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
}

因为我知道 IDictionary 实际上是一个 Dictionary< TKey, TValue> 下面,也许我可以做一些反射魔法来获得设置器?

标签: c#dictionaryreflectionidictionary

解决方案


当您枚举 a 的条目时DictionaryKey并从的内部结构 ( )Value复制到or的新实例。因此尝试修改这些是徒劳的,因为这些更改不会传播回字典。要修改,您必须使用它的 indexer 或,或类似的方法。DictionaryEntry[]KeyValuePairDictionaryEntryDictionaryEntryDictionaryAddRemove

C# 索引器只是使用Dictionary<TKey,TValue>.Item属性的语法糖。所以在使用反射时,你必须改用这个属性。

要为 a 中的每个项目创建值设置器,您需要为每个项目获取一个键,然后在使用它的属性设置新值时将Dictionary其用作参数。创建密钥设置器更加困难,因为不支持更改现有密钥。您要做的实际上是从 中删除现有项目并使用新密钥插入一个新项目:indexDictionaryItemDictionaryDictionary

// 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);
}

推荐阅读