2012-03-15 2 views
3

я принципиально хочу сделать это:Как получить IEnumerable <DictionaryEntry> из IDictionary?

if(obj is IDictionary) 
{ 
    return "{" + string.Join(Environment.NewLine, ((IDictionary)obj).Cast<DictionaryEntry>().Select(e => string.Format(" {0}: {1}", PrettyString(e.Key), PrettyString(e.Value)))) + "}"; 
} 

Но я получаю неверный бросок. Я могу сделать

foreach(DictionaryEntry e in (IDictionary)obj) 

Так почему я не могу это сделать?


Мне это очень интересно. Я написал расширение, чтобы решить эту проблему:

static class EnumerableExt 
{ 
    public static IEnumerable<DictionaryEntry> Entries(this IDictionary dict) 
    { 
     foreach (var item in dict) yield return (DictionaryEntry)item; 
    } 
} 

Тогда я могу просто сделать:

((IDictionary)obj).Entries().Select(...) 

Любопытная часть что Resharper говорит мне, что я могу заменить мое расширение с этим:

return dict.Cast<DictionaryEntry>(); 

Но это исключает. Это ошибка в Resharper? Или как работает Cast? Я бы предположил, что Cast будет работать точно так же, как мое расширение.

Редактировать: Ah .. Я прочитал this answer немного ближе. Все еще странно.

+0

Что такое 'obj' от вас код? Еще один контекст поможет. –

+0

@ M.Babcock: Учитывая, что он находится внутри блока 'if (obj is IDictionary)', это гарантированно будет 'IDictionary'. Я тестирую 'Dictionary ', если это имеет значение. Фактическим типом 'obj' является' object'. – mpen

+0

Только совет: вам гораздо лучше использовать общую версию с помощью KeyValuePair ' – porges

ответ

4

являются ключ и значение типа известны во время компиляции? Если да, то вы могли бы сделать общий метод для довольно-печати, и пусть компилятор выводить типы для вас, как:

static void Main(string[] args) 
{ 
    var obj = new Dictionary<string, string>() 
     { 
      { "foo", "bar" }, 
      { "baz", "ack" }, 
     }; 

    var stringVersion = GetStringVersion(obj); 

    Console.WriteLine(stringVersion); 
} 

private static string GetStringVersion<TKey, TValue>(Dictionary<TKey, TValue> dict) 
{ 
    return "{" + string.Join(Environment.NewLine, dict.Select(e => string.Format(" {0}: {1}", e.Key, e.Value))) + "}"; 
} 

В терминах поведения вы видите, это известный странный случай из-за назад Compat и дизайнерское решение:

http://blogs.msdn.com/b/bclteam/archive/2004/09/03/225473.aspx

Мы обсудили проблему с реализацией IEnumerable на словаря. Какой тип должен быть IEnumerable.GetEnumerator(). Текущий return? KeyValuePair или DictionaryEntry? То же самое для ICollection.CopyTo. Экземпляры какого типа следует скопировать в массив ?

Мы решили:

  1. IEnumerable и ICollection реализации интерфейса будет использовать KeyValuePair<K,V> как тип элемента.
  2. IDictionary конкретных членов (GetEnumerator возвращение IDictionaryEnumerator) будет использовать DictionaryEntry в качестве типа элемента.

Причина заключается в том, что мы находимся в процессе внесения изменений, где IEnumerator<T> бы продлить IEnumerator. Было бы очень странно, если бы ходьба по иерархии с Dictionary<K,V> ->IEnumerable<T> ->IEnumerable, мы неожиданно изменили тип элемента, возвращаемого из счетчиков.

Благодаря этому дизайнерского решения (при создании .NET 2.0), при работе на нетипичных IEnumerable, то GetEnumerator() возвращает IDictionaryEnumerator, и результирующие элементы типа DictionaryEntry (предположительно для back-compat с итерацией типа Hashtable). Однако, как только вы вызываете общий IEnumerable (это то, что происходит, когда вы делаете Cast() или OfType(), вместо этого вы получите элементы KeyValuePair.

Обратите внимание, что это особая странность в использовании словаря в не- -generic context - Hashtable (по-прежнему) выполняет итерацию как элементов DictionaryEntry, и «нормальная» итерация по словарю дает вам элементы KeyValuePair.

Как уже говорилось, вы должны придерживаться общего доступа, если сможете, Позвольте вам избежать этой маленькой «gotcha» в дизайнерских решениях словаря.

+0

Это жизнеспособно. Я не знаю, почему мне не приходило в голову сделать весь метод PrettyPrint общим. Я все еще могу использовать vanilla 'IDictionary' как резерв. Я бы по-прежнему использовал 'IDictionary ' в примере, который вы предоставляете. – mpen

+0

Отличное редактирование. Это объясняет странность намного лучше. – mpen

1

Вы пытаетесь превратить IDictionary в DictionaryEntry. В цикле foreach вы не кладете IDictionary, а элементы, содержащиеся в нем.

Вы можете сделать это и использовать список:

var list = new List<DictionaryEntry>(); 
foreach (DictionaryEntry de in (IDictionary)obj) 
    list.Add(de); 
return "{" + string.Join(Environment.NewLine, list.Select(e => string.Format(" {0}: {1}", PrettyString(e.Key), PrettyString(e.Value)))) + "}"; 
+0

Я думал, что могло бы произойти, но я подумал, что '.Cast' работает с каждым элементом в IEnumerable. Несмотря ни на что, как мне это исправить? '.GetEnumerator(). Cast <>', похоже, не вещь. – mpen

+0

Смотрите мой код выше. Надеюсь, что это сработает, извините, я его не тестировал. – joshuahealy

+0

'((IDictionary) obj) .Выбрать' не существует. 'IDictionary' реализует' IEnumerable' не 'IEnumerable ', в противном случае у меня не было бы проблем;) – mpen

1

Я проголосовал, чтобы закрыть этот вопрос, так как this answer достаточно хорош.

Это мое решение:

static class EnumerableExt 
{ 
    public static IEnumerable<DictionaryEntry> Entries(this IDictionary dict) 
    { 
     foreach (var item in dict) yield return (DictionaryEntry)item; 
    } 
} 

Обратите внимание, что

return dict.Cast<DictionaryEntry>(); 

ли не работу здесь, несмотря на то, что Resharper говорит вам.