如何遍历字典?

我已经看到了几种在C#中迭代字典的不同方法。有标准的方法吗?

2186777 次浏览
foreach(KeyValuePair<string, string> entry in myDictionary){// do something with entry.Value or entry.Key}

我会说foreach是标准的方法,尽管它显然取决于你在寻找什么

foreach(var kvp in my_dictionary) {...}

这就是你要找的吗?

如果您尝试在C#中使用泛型字典,就像在另一种语言中使用关联数组一样:

foreach(var item in myDictionary){foo(item.Key);bar(item.Value);}

或者,如果您只需要遍历密钥集合,请使用

foreach(var item in myDictionary.Keys){foo(item);}

最后,如果您只对值感兴趣:

foreach(var item in myDictionary.Values){foo(item);}

(请注意,var关键字是可选的C#3.0及以上功能,您也可以在此处使用键/值的确切类型)

有很多选择。我个人最喜欢的是KeyValuePair

Dictionary<string, object> myDictionary = new Dictionary<string, object>();// Populate your dictionary here
foreach (KeyValuePair<string,object> kvp in myDictionary){// Do some interesting things}

您还可以使用键和值集合

取决于你是在找键还是值…

从MSDN#0类描述:

// When you use foreach to enumerate dictionary elements,// the elements are retrieved as KeyValuePair objects.Console.WriteLine();foreach( KeyValuePair<string, string> kvp in openWith ){Console.WriteLine("Key = {0}, Value = {1}",kvp.Key, kvp.Value);}
// To get the values alone, use the Values property.Dictionary<string, string>.ValueCollection valueColl =openWith.Values;
// The elements of the ValueCollection are strongly typed// with the type that was specified for dictionary values.Console.WriteLine();foreach( string s in valueColl ){Console.WriteLine("Value = {0}", s);}
// To get the keys alone, use the Keys property.Dictionary<string, string>.KeyCollection keyColl =openWith.Keys;
// The elements of the KeyCollection are strongly typed// with the type that was specified for dictionary keys.Console.WriteLine();foreach( string s in keyColl ){Console.WriteLine("Key = {0}", s);}

如果说,你想在默认情况下遍历值集合,我相信你可以实现IENumable<>,其中T是字典中值对象的类型,“this”是字典。

public new IEnumerator<T> GetEnumerator(){return this.Values.GetEnumerator();}

我在MSDN上DictionaryBase类的留档中找到了这个方法:

foreach (DictionaryEntry de in myDictionary){//Do some stuff with de.Value or de.Key}

这是我能够在继承自DictionaryBase的类中正确运行的唯一一个。

在某些情况下,您可能需要一个可能由for循环实现提供的计数器。为此,LINQ提供了#0,它启用了以下功能:

for (int index = 0; index < dictionary.Count; index++) {var item = dictionary.ElementAt(index);var itemKey = item.Key;var itemValue = item.Value;}

有时,如果您只需要要枚举的值,请使用字典的值集合:

foreach(var value in dictionary.Values){// do something with entry.Value only}

这篇文章指出这是最快的方法:http://alexpinsker.blogspot.hk/2010/02/c-fastest-way-to-iterate-over.html

我很欣赏这个问题已经有了很多回应,但我想投入一点研究。

与迭代数组之类的东西相比,迭代字典可能相当慢。在我的测试中,迭代数组需要0.015003秒,而迭代字典(元素数量相同)需要0.0365073秒,是2.4倍!虽然我看到了更大的差异。相比之下,List介于0.00215043秒之间。

然而,这就像比较苹果和橘子。我的观点是,迭代字典很慢。

字典针对查找进行了优化,因此考虑到这一点,我创建了两个方法。一个简单地做一个foreach,另一个迭代键然后查找。

public static string Normal(Dictionary<string, string> dictionary){string value;int count = 0;foreach (var kvp in dictionary){value = kvp.Value;count++;}
return "Normal";}

这个加载键并迭代它们(我也尝试将键拉入字符串[],但差异可以忽略不计。

public static string Keys(Dictionary<string, string> dictionary){string value;int count = 0;foreach (var key in dictionary.Keys){value = dictionary[key];count++;}
return "Keys";}

在这个例子中,普通的foreach测试需要0.0310062,密钥版本需要0.2205441。加载所有密钥并迭代所有查找显然要慢得多!

对于最后的测试,我已经执行了十次迭代,看看使用这里的键是否有任何好处(到目前为止,我只是好奇):

这是RunTest方法,如果它可以帮助您可视化正在发生的事情。

private static string RunTest<T>(T dictionary, Func<T, string> function){DateTime start = DateTime.Now;string name = null;for (int i = 0; i < 10; i++){name = function(dictionary);}DateTime end = DateTime.Now;var duration = end.Subtract(start);return string.Format("{0} took {1} seconds", name, duration.TotalSeconds);}

在这里,正常的foreach运行耗时0.2820564秒(如您所料,大约是单次迭代的十倍)。对键的迭代耗时2.2249449秒。

编辑添加:阅读其他一些答案让我质疑如果我使用字典而不是字典会发生什么。在这个例子中,数组花了0.0120024秒,列表花了0.0185037秒,字典花了0.0465093秒。可以合理地期望数据类型会影响字典的速度。

我的结论是什么

  • 如果可以,请避免迭代字典,它们比迭代包含相同数据的数组慢得多。
  • 如果你确实选择遍历字典,不要试图太聪明,尽管速度较慢,你可能会比使用标准的Foreach方法更糟糕。

我将利用. NET 4.0+并对最初接受的答案提供更新的答案:

foreach(var entry in MyDic){// do something with entry.Value or entry.Key}
var dictionary = new Dictionary<string, int>\{\{ "Key", 12 }};
var aggregateObjectCollection = dictionary.Select(entry => new AggregateObject(entry.Key, entry.Value));

您还可以在用于多线程处理的大型字典上尝试此操作。

dictionary.AsParallel().ForAll(pair =>{// Process pair.Key and pair.Value here});

一般来说,在没有特定上下文的情况下询问“最佳方式”就像询问什么是最好的颜色?

一方面,有很多颜色,没有最好的颜色。这取决于需要,通常也取决于口味。

另一方面,有很多方法可以在C#中迭代字典,但没有最好的方法。这取决于需要,通常也取决于口味。

最直接的方式

foreach (var kvp in items){// key is kvp.KeydoStuff(kvp.Value)}

如果您只需要该值(允许将其称为item,比kvp.Value更具可读性)。

foreach (var item in items.Values){doStuff(item)}

如果您需要特定的排序顺序

一般来说,初学者对词典的枚举顺序感到惊讶。

LINQ提供了一个简洁的语法,允许指定顺序(以及许多其他事情),例如:

foreach (var kvp in items.OrderBy(kvp => kvp.Key)){// key is kvp.KeydoStuff(kvp.Value)}

同样,你可能只需要该值。LINQ还提供了一个简洁的解决方案:

  • 直接迭代值(允许调用它item,比kvp.Value更具可读性)
  • 但按钥匙排序

这里是:

foreach (var item in items.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value)){doStuff(item)}

您可以从这些示例中执行更多真实世界的用例。如果您不需要特定的订单,只需坚持“最直接的方式”(见上文)!

根据MSDN上的官方留档,迭代字典的标准方法是:

foreach (DictionaryEntry entry in myDictionary){//Read entry.Key and entry.Value here}

只是想加上我的2美分,因为大多数答案都与Foreach-loop有关。请查看以下代码:

Dictionary<String, Double> myProductPrices = new Dictionary<String, Double>();
//Add some entries to the dictionary
myProductPrices.ToList().ForEach(kvP =>{kvP.Value *= 1.15;Console.Writeline(String.Format("Product '{0}' has a new price: {1} $", kvp.Key, kvP.Value));});

虽然这增加了一个额外的调用'. ToList()',但可能会有轻微的性能提升(正如这里所指出的Foreach://比较列表中的值//Foreach(){}),特别是在处理大型字典和并行运行时,没有选择/根本不会产生影响。

另外,请注意,您将无法在Foreach循环中为“Value”属性赋值。另一方面,您也可以操纵“Key”,可能会在运行时遇到麻烦。

当你只想“读取”键和值时,你也可以使用IENumable.选择()。

var newProductPrices = myProductPrices.Select(kvp => new { Name = kvp.Key, Price = kvp.Value * 1.15 } );

迭代字典的最简单形式:

foreach(var item in myDictionary){Console.WriteLine(item.Key);Console.WriteLine(item.Value);}

使用.NET Framework 4.7可以使用分解

var fruits = new Dictionary<string, int>();...foreach (var (fruit, number) in fruits){Console.WriteLine(fruit + ": " + number);}

要使此代码在较低的C#版本上工作,请添加System.ValueTuple NuGet package并在某处写入

public static class MyExtensions{public static void Deconstruct<T1, T2>(this KeyValuePair<T1, T2> tuple,out T1 key, out T2 value){key = tuple.Key;value = tuple.Value;}}

字典它是c#中的一个泛型集合类,它以键值格式存储数据。键必须是唯一的,不能为空,而值可以是重复的和空的。由于字典中的每个项目都被视为代表键及其值的KeyValuePair结构。因此我们应该在元素迭代时采用元素类型KeyValuePair下面是一个例子。

Dictionary<int, string> dict = new Dictionary<int, string>();dict.Add(1,"One");dict.Add(2,"Two");dict.Add(3,"Three");
foreach (KeyValuePair<int, string> item in dict){Console.WriteLine("Key: {0}, Value: {1}", item.Key, item.Value);}

使用c#7,将此延拓法添加到解决方案的任何项目中:

public static class IDictionaryExtensions{public static IEnumerable<(TKey, TValue)> Tuples<TKey, TValue>(this IDictionary<TKey, TValue> dict){foreach (KeyValuePair<TKey, TValue> kvp in dict)yield return (kvp.Key, kvp.Value);}}


并使用这个简单的语法

foreach (var(id, value) in dict.Tuples()){// your code using 'id' and 'value'}


或者这个,如果你喜欢

foreach ((string id, object value) in dict.Tuples()){// your code using 'id' and 'value'}


而不是传统的

foreach (KeyValuePair<string, object> kvp in dict){string id = kvp.Key;object value = kvp.Value;
// your code using 'id' and 'value'}


扩展方法将IDictionary<TKey, TValue>KeyValuePair转换为强类型的tuple,允许您使用这种新的舒适语法。

它将所需的字典条目转换为tuples,因此它不会将整个字典转换为tuples,因此没有与此相关的性能问题。

与直接使用KeyValuePair相比,调用扩展方法创建tuple的成本很小,如果您将KeyValuePair的属性KeyValue分配给新的循环变量,这应该不是问题。

在实践中,这种新语法非常适合大多数情况,除了低级超高性能场景,您仍然可以选择在特定位置不使用它。

看看这个:MSDN Blog-C#7中的新功能

我写了一个扩展来遍历字典。

public static class DictionaryExtension{public static void ForEach<T1, T2>(this Dictionary<T1, T2> dictionary, Action<T1, T2> action) {foreach(KeyValuePair<T1, T2> keyValue in dictionary) {action(keyValue.Key, keyValue.Value);}}}

然后你可以打电话

myDictionary.ForEach((x,y) => Console.WriteLine(x + " - " + y));

从C#7开始,您可以将对象解构为变量。我相信这是迭代字典的最佳方式。

示例:

KeyValuePair<TKey, TVal>上创建一个解构它的扩展方法:

public static void Deconstruct<TKey, TVal>(this KeyValuePair<TKey, TVal> pair, out TKey key, out TVal value){key = pair.Key;value = pair.Value;}

通过以下方式迭代任何Dictionary<TKey, TVal>

// Dictionary can be of any types, just using 'int' and 'string' as examples.Dictionary<int, string> dict = new Dictionary<int, string>();
// Deconstructor gets called here.foreach (var (key, value) in dict){Console.WriteLine($"{key} : {value}");}

除了最高级别的职位,其中有使用之间的讨论

foreach(KeyValuePair<string, string> entry in myDictionary){// do something with entry.Value or entry.Key}

foreach(var entry in myDictionary){// do something with entry.Value or entry.Key}

最完整的是以下内容,因为您可以从初始化中看到字典类型,kvp是KeyValuePair

var myDictionary = new Dictionary<string, string>(x);//fill dictionary with x
foreach(var kvp in myDictionary)//iterate over dictionary{// do something with kvp.Value or kvp.Key}

C#7.0引入了解构函数并且如果您使用的是. NET Core 2.0+ Application,则structKeyValuePair<>已经为您包含了Deconstruct()。所以你可以这样做:

var dic = new Dictionary<int, string>() { { 1, "One" }, { 2, "Two" }, { 3, "Three" } };foreach (var (key, value) in dic) {Console.WriteLine($"Item [{key}] = {value}");}//Orforeach (var (_, value) in dic) {Console.WriteLine($"Item [NO_ID] = {value}");}//Orforeach ((int key, string value) in dic) {Console.WriteLine($"Item [{key}] = {value}");}

在此处输入图片描述

我知道这是一个非常古老的问题,但我创建了一些可能有用的扩展方法:

    public static void ForEach<T, U>(this Dictionary<T, U> d, Action<KeyValuePair<T, U>> a){foreach (KeyValuePair<T, U> p in d) { a(p); }}
public static void ForEach<T, U>(this Dictionary<T, U>.KeyCollection k, Action<T> a){foreach (T t in k) { a(t); }}
public static void ForEach<T, U>(this Dictionary<T, U>.ValueCollection v, Action<U> a){foreach (U u in v) { a(u); }}

这样我就可以编写这样的代码:

myDictionary.ForEach(pair => Console.Write($"key: {pair.Key}, value: {pair.Value}"));myDictionary.Keys.ForEach(key => Console.Write(key););myDictionary.Values.ForEach(value => Console.Write(value););

foreach是最快的,如果你只迭代___.Values,它也更快

在此处输入图片描述

如果你想使用for循环,你可以这样做:

var keyList=new List<string>(dictionary.Keys);for (int i = 0; i < keyList.Count; i++){var key= keyList[i];var value = dictionary[key];}

正如在回答上已经指出的,KeyValuePair<TKey, TValue>实现了从. NET Core 2.0、. NET Standard 2.1和. NET Framework 5.0(预览版)开始的#1方法。

有了这个,可以以KeyValuePair不可知的方式迭代字典:

var dictionary = new Dictionary<int, string>();
// ...
foreach (var (key, value) in dictionary){// ...}

最好的答案当然是:想一想,如果您计划迭代它,您可以使用比字典更合适的数据结构-正如Vikas Gupta在问题下的(开始)讨论中已经提到的那样。但是,整个讨论仍然缺乏令人惊讶的好选择。一个是:

SortedList<string, string> x = new SortedList<string, string>();
x.Add("key1", "value1");x.Add("key2", "value2");x["key3"] = "value3";foreach( KeyValuePair<string, string> kvPair in x )Console.WriteLine($"{kvPair.Key}, {kvPair.Value}");

为什么它可以被认为是迭代字典的代码气味(例如,通过foreach(KeyValuePair<,>)?

清洁编码的基本原则:"表达意图!"Robert C. Martin在《干净的代码》中写道:“选择能揭示意图的名称”。显然,单独命名太弱了。“表达(揭示)每个编码决策的意图”更好地表达了它。

一个相关的原则是“最小意外原则”(=最小惊讶原则)。

为什么这与遍历字典有关?选择字典表达了选择数据结构的意图,该数据结构主要用于通过键查找数据。现在. NET中有很多替代品,如果您想遍历键/值对,您可以选择其他东西。

此外:如果你迭代某些东西,你必须揭示一些关于项目是如何(被)订购和预期被订购的东西!尽管字典的已知实现按照添加项的顺序对密钥集合进行排序-AFAIK,字典没有关于排序的可靠规范(有吗?)。

但是有哪些替代方案呢?

TLDR:
SortedList:如果您的集合没有变得太大,一个简单的解决方案是使用SortedList<,>,它还为您提供键/值对的完整索引。

微软有一篇关于提及和解释拟合集合的长文:
钥匙收藏

提到最重要的:KeyedCollection<,>和SortedDicpedia<,>。分类字典<,>比SortedList快一点,因为只有在它变大时才会插入,但缺乏索引,只有在O(log n)用于插入而不是其他操作时才需要。如果你真的需要O(1)用于插入并接受较慢的迭代作为交换,你必须使用简单的字典<,>。显然,对于所有可能的操作,没有一种数据结构是最快的…

另外还有不可变排序字典<,>。

如果一个数据结构不完全是你需要的,那么从Dicpedia<,>甚至从新的字典名称<,>中派生,并添加显式的迭代/排序函数!