题 迭代字典的最佳方法是什么?


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


1949
2017-09-26 18:20


起源


令我感到惊讶的是,这个问题有很多答案,还有一个上升了923次...(使用foreach ofcourse)..我认为,或者至少补充一点,如果你必须迭代字典,很可能你是使用它不正确/不恰当..我不得不做出这样的评论,因为我看到字典被滥用的方式恕我直言不合适...是的,当你迭代字典,而不是查找,这是罕见的情况,这是什么它是专为..请记住,在想知道如何迭代字典之前。 - Vikas Gupta
@VikasGupta当你不知道密钥是什么时,你会建议用一组键值对做什么? - nasch
@nasch: myDictionary.Keys 会给你一个包含密钥的集合 myDictionary。 - displayName
@displayName如果你想对每个键值对做一些事情,但没有引用用于查找值的键,你会迭代字典,对吧?我只是指出,有时候你会想要这样做,尽管Vikas声称这通常是不正确的用法。 - nasch
要说这是不正确的用法意味着有一个更好的选择。那是什么选择? - Kyle Delaney


答案:


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

2901
2017-09-26 18:22



如果我不完全知道Dictionary中键/值的类型怎么办?运用 var entry在这种情况下更好,因此我投了票 这个答案 在第二次看而不是上面的。 - Ozair Kafray
@OzairKafray使用 var 当你不知道类型一般是不好的做法。 - Nate
这个答案是优越的,因为Pablo没有默认使用混淆返回类型的惰性编码器“var”用法。 - MonkeyWrench
@MonkeyWrench:嗯。 Visual Studio知道类型是什么;所有你需要做的就是将鼠标悬停在变量上以找出答案。 - Robert Harvey♦
据我了解, var 仅在编译时已知类型时才有效。如果Visual Studio知道类型,那么您也可以找到它。 - Kyle Delaney


如果您尝试在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 keyword是一个可选的C#3.0及以上功能,你也可以在这里使用你的键/值的确切类型)


646
2017-09-26 18:22



你的第一个代码块最需要var功能:) - nawfal
我很欣赏这个答案指出你可以明确地迭代键或值。 - Rotsiser Mho
我不喜欢在这里使用var。鉴于它只是语法糖,为什么在这里使用它?当有人试图读取代码时,他们将不得不跳过代码来确定代码类型 myDictionary (除非那是当然的实际名称)。我认为当类型明显时使用var是好的,例如 var x = "some string" 但是当它没有立即显而易见时,我认为它是懒惰的编码会伤害代码阅读器/审阅者 - James Wierzba
var 在我看来应该谨慎使用。特别是在这里,它不具有建设性:类型 KeyValuePair 可能与这个问题有关。 - Sinjai


在某些情况下,您可能需要一个可以通过for循环实现提供的计数器。为此,LINQ提供 ElementAt 这使得以下内容:

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

107
2018-03-10 20:44



要使用'.ElementAt'方法,请记住:使用System.Linq;这不包括在fx中。自动生成的测试类。 - Tinia
如果要修改与键关联的值,则可以使用此方法。否则,在修改和使用foreach()时会抛出异常。 - Mike de Klerk
使用时要小心。看这里: stackoverflow.com/a/2254480/253938 - RenniePet
是不是 ElementAt O(n)操作? - Arturo Torres Sánchez
这个答案完全不值得这么多的赞成。字典没有隐式顺序,所以使用 .ElementAt 在这种情况下可能会导致微妙的错误。更重要的是阿图罗的观点。你将迭代字典 dictionary.Count + 1 对于应该仅为O(n)的操作,导致O(n ^ 2)复杂度的时间。如果你真的需要一个索引(如果你这样做,你可能首先使用了错误的集合类型),你应该迭代 dictionary.Select( (kvp, idx) => new {Index = idx, kvp.Key, kvp.Value}) 而不是使用 .ElementAt 在循环内。 - spender


取决于你是否在关键或价值观之后......

来自MSDN Dictionary(TKey, TValue) 课程描述:

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

75
2017-09-26 18:27





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

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

另一方面,有许多方法可以在C#中迭代一个Dictionary而且没有最好的方法。这取决于需要,也经常取决于口味。

最直截了当的方式

foreach (var kvp in items)
{
    // key is kvp.Key
    doStuff(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.Key
    doStuff(kvp.Value)
}

您可能只需要该值。 LINQ还提供简洁的解决方案:

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

这里是:

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

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


55
2017-08-10 11:15



最后一个应该是 .Values 而不是选择条款。 - Mafii
@Mafii你确定吗? OrderBy返回的值不是KeyValuePair类型,它们没有 Value 领域。我在这里看到的确切类型是 IOrderedEnumerable<KeyValuePair<TKey, TValue>>。也许你的意思是别的?你能写一个完整的行来表明你的意思(并测试它)吗? - Stéphane Gourichon
我认为这个答案包含了我的意思: stackoverflow.com/a/141105/5962841 但如果我困惑的话,请纠正我 - Mafii
@Mafii重新阅读我的整个答案,代码部分之间的解释说明了上下文。您提到的答案就像我的答案中的第二个代码部分(无需订单)。在那里我写了 items.Value 像你建议的那样。在您评论的第四部分的情况下, Select()是一种导致的方式 foreach 直接枚举字典中的值而不是键值对。如果不知何故你不喜欢 Select() 在这种情况下,您可能更喜欢第三个代码部分。第四部分的要点是表明可以使用LINQ预处理集合。 - Stéphane Gourichon
如果你这样做 .Keys.Orderby() 你将迭代一个键列表。如果这就是你需要的一切,那很好。如果您需要值,那么在循环中您必须查询每个键上的字典以获取值。在许多情况下,它不会产生实际差异。在高性能场景中,它会。就像我在回答的开头所写的那样:“有很多方法(......)并且没有最好的方法。这取决于需要,而且往往也取决于品味。” - Stéphane Gourichon


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

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

这就是你要找的东西吗?


36
2017-09-26 18:22



嗯,是不是把项目“价值”命名为相当混乱?您通常会使用类似“value.Key”和“value.Value”的语法,这对于将要阅读代码的任何其他人来说都不是很直观,特别是如果他们不熟悉.Net词典的实现方式。 - RenniePet
@RenniePet kvp 在迭代字典和相关数据结构时,通常用于命名KeyValuePair实例: foreach(var kvp in myDictionary){...。 - mbx


您也可以在大字典上尝试使用多线程处理。

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

28
2018-06-11 13:32



@WiiMaxx,如果这些项目不相互依赖,则更为重要 - Mafii


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

Dictionary<string, object> myDictionary = new Dictionary<string, object>();
// Populate your dictionary here

foreach (KeyValuePair<string,object> kvp in myDictionary)
{
     // Do some interesting things
}

您还可以使用键和值集合


22
2017-09-26 18:22





我很欣赏这个问题已经有了很多回复,但我想进行一些研究。

与迭代类似数组的东西相比,迭代字典可能相当慢。在我的测试中,对数组的迭代花费了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秒。

编辑添加: 阅读其他一些答案让我怀疑如果我使用Dictionary而不是Dictionary,会发生什么。在此示例中,数组占用0.0120024秒,列表0.0185037秒,字典0.0465093秒。期望数据类型对字典的缓慢程度产生影响是合理的。

我的结论是什么?

  • 如果可以的话,避免迭代字典,它们比在数组中使用相同数据进行迭代要慢得多。
  • 如果你确实选择迭代字典,不要试图太聪明,虽然速度比使用标准的foreach方法要差很多。

22
2017-07-30 10:54



您应该使用StopWatch而不是DateTime进行测量: hanselman.com/blog/... - Even Mien
你能不能描述一下你的测试场景,字典中有多少项,你多久运行一次你的场景来计算平均时间,...... - WiiMaxx
有趣的是,根据字典中的数据,您将得到不同的结果。在对字典进行迭代时,Enumerator函数必须跳过字典中的大量空槽,这导致它比迭代遍历数组慢。如果字典已满,则跳过的空插槽将少于半空插槽。 - Martin Brown


您建议在下面进行迭代

Dictionary<string,object> myDictionary = new Dictionary<string,object>();
//Populate your dictionary here

foreach (KeyValuePair<string,object> kvp in myDictionary) {
    //Do some interesting things;
}

仅供参考, foreach 如果值是object类型,则不起作用。


9
2017-10-28 20:49



请详细说明: foreach 如果 哪一个 值是类型 object?否则这没有多大意义。 - Marc L.


.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;
    }
}

8
2017-10-17 15:18



这是不正确的。 .NET 4.7就是这样 ValueTuple 内置。它可以作为早期版本的nuget包。更重要的是,需要C#7.0+ Deconstruct 作为解构函数的方法 var (fruit, number) in fruits。 - David Arno