Перечислить ключи и значения IDictionary, когда я не знаю тип значений

У меня есть ссылка на объект. Я знаю, что это соответствует

IDictionary<string, T> 

для некоторого типа T. (Он может не соответствовать простому IDictionary или IReadyOnlyDictionary). Все, что я знаю о T, это то, что он происходит от объекта. Как я могу получить его ключи и получить значение для ключа? (Я согласен с тем, что значение возвращается как объект, а не как T. Я также согласен с тем, что никогда не узнаю, что такое T.)

То, что я хочу написать, но не могу, это что-то вроде этого:

public void SomeMethod(object reallyADict) { // reallyADict implements IDictionary<string, T>.
  foreach (string key in reallyADict.Keys) {
    object value = reallyADict[key];
    // . . .
  }
}

**

По запросу пример класса приведен ниже.

using System;
using System.Collections.Generic;
using System.Collections;

namespace My.Collections
{
  public class WrappedDictionary: IDictionary<string, int>
  {
    public WrappedDictionary() {
      this.InnerDictionary = new Dictionary<string, int>{ {"one", 1}, {"two", 2 }};
    }
    private Dictionary<string, int> InnerDictionary { get; set;}

    private ICollection<KeyValuePair<string, int>> InnerCollection {
      get {
        return this.InnerDictionary;
      }
    }


    #region IDictionary implementation
    void IDictionary<string, int>.Add(string key, int value) {
      this.InnerDictionary.Add(key, value);
    }
    bool IDictionary<string, int>.ContainsKey(string key) {
      return this.InnerDictionary.ContainsKey(key);
    }
    bool IDictionary<string, int>.Remove(string key) {
      return this.InnerDictionary.Remove(key);
    }
    bool IDictionary<string, int>.TryGetValue(string key, out int value) {
      return this.InnerDictionary.TryGetValue(key, out value);
    }
    int IDictionary<string, int>.this[string index] {
      get {
        return this.InnerDictionary[index];
      }
      set {
        this.InnerDictionary[index] = value;
      }
    }
    ICollection<string> IDictionary<string, int>.Keys {
      get {
        return this.InnerDictionary.Keys;
      }
    }
    ICollection<int> IDictionary<string, int>.Values {
      get {
        return this.InnerDictionary.Values;
      }
    }
    #endregion
    #region ICollection implementation
    void ICollection<KeyValuePair<string, int>>.Add(KeyValuePair<string, int> item) {
      this.InnerCollection.Add(item);
    }
    void ICollection<KeyValuePair<string, int>>.Clear() {
      this.InnerDictionary.Clear();
    }
    bool ICollection<KeyValuePair<string, int>>.Contains(KeyValuePair<string, int> item) {
      return this.InnerCollection.Contains(item);
    }
    void ICollection<KeyValuePair<string, int>>.CopyTo(KeyValuePair<string, int>[] array, int arrayIndex) {
      this.InnerCollection.CopyTo(array, arrayIndex);
    }
    bool ICollection<KeyValuePair<string, int>>.Remove(KeyValuePair<string, int> item) {
      return this.InnerCollection.Remove(item);
    }
    int ICollection<KeyValuePair<string, int>>.Count {
      get {
        return this.InnerCollection.Count;
      }
    }
    bool ICollection<KeyValuePair<string, int>>.IsReadOnly {
      get {
        return this.InnerCollection.IsReadOnly;
      }
    }
    #endregion
    #region IEnumerable implementation
    IEnumerator<KeyValuePair<string, int>> IEnumerable<KeyValuePair<string, int>>.GetEnumerator() {
      return this.InnerCollection.GetEnumerator();
    }
    #endregion
    #region IEnumerable implementation
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
      return (this as IEnumerable).GetEnumerator();
    }
    #endregion
  }
}

person William Jockusch    schedule 05.08.2016    source источник
comment
Вы пытались сделать метод универсальным? Что-то вроде SomeMethod<T>(IDictionary<string,T> reallyADict)?   -  person Manfred Radlwimmer    schedule 05.08.2016
comment
У меня есть ссылка на realADict как на объект, а не как на Dictionary‹string, T›.   -  person William Jockusch    schedule 05.08.2016
comment
Возможный дубликат stackoverflow.com/questions/141088/   -  person DarkMakukudo    schedule 05.08.2016


Ответы (1)


К сожалению, вы не можете привести reallyADict к чему-то вроде Dictionary<string,T>, потому что вам нужен определенный тип.

И комментарий Манфреда, чтобы использовать общий метод, например

public IEnumerable<T> SomeMethod<T>(Dictionary<string, T> dict)

был бы и мой подход. Но вы заявили, что у вас действительно есть словарь только как object.

Итак, я решил это с помощью Reflection:

public IEnumerable<object> SomeMethod(object reallyADict)
{
    Type genericInterface = reallyADict?.GetType().GetInterface("IDictionary`2");

    PropertyInfo propKeys = genericInterface?.GetProperty("Keys");
    if (propKeys?.GetMethod == null) yield break;

    IEnumerable<string> keys = (IEnumerable<string>)propKeys.GetValue(reallyADict);

    PropertyInfo propIndex = genericInterface.GetProperty("Item");
    if (propIndex?.GetMethod == null) yield break;

    foreach (string key in keys)
        yield return propIndex.GetMethod.Invoke(reallyADict, new object[] { key });
}

Этот метод получает свойство Keys из свойства reallyDict (если оно есть) и использует его как свойство IEnumerable<string>.

Затем он перебирает все эти ключи и использует свойство индексатора базового словаря, чтобы вернуть значение. Свойство индексатора имеет имя Item.

person René Vogt    schedule 05.08.2016
comment
@ManfredRadlwimmer Нет. GetMethod является собственностью PropertyInfo. Это MethodInfo, который представляет геттер этого свойства. В этой строке я не хочу получать геттер (у меня он уже есть в GetMethod), я просто хочу вызывать этот геттер. - person René Vogt; 05.08.2016
comment
Если вы используете более старую версию .NET, вы можете использовать GetGetMethod() вместо GetMethod (доступно с версии 4.5). - person Manfred Radlwimmer; 05.08.2016
comment
@ManfredRadlwimmer только что проверил это, да, GetGetMethod() вернет MethodInfo для геттера, но GetMethod - это просто свойство, которое возвращает то же самое. Вы можете выбрать, как вам нравится ;) - person René Vogt; 05.08.2016
comment
Хм, интересно, когда я вызываю действительно ADict.GetType().GetRuntimeProperties(), я не вижу ни одной с именем Keys, но вижу одну с именем System.Collections.Generic.IDictionary‹System.String,T› .Keys, где T — фактическое значение T . . . - person William Jockusch; 05.08.2016
comment
@WilliamJockusch Я использую GetProperty, а не GetRuntimeProperties() (хотя я не знаю, не посмотрев, в чем разница). Можете ли вы показать пример класса, с которым вы это вызываете? Я тестировал только настоящий Dictionary<string,int>, возможно, нам нужно специальное имя вместо Key, если у вас есть какой-то пользовательский класс, реализующий только IDictionary<string,someType>. - person René Vogt; 05.08.2016
comment
@WilliamJockusch протестировал его с пользовательской реализацией IDictionary<string,someType>, и он отлично работает. - person René Vogt; 05.08.2016
comment
@WilliamJockusch А, только что увидел твою правку. Проблема в том, что вы используете явную реализацию интерфейса. Это усложняет ситуацию... Я постараюсь улучшить ответ, когда найду время... это может занять некоторое время. - person René Vogt; 05.08.2016
comment
@WilliamJockusch решил это и обновил ответ. Хитрость заключается в том, чтобы получить interface's type and get the PropertyInfo из этого типа. - person René Vogt; 05.08.2016