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

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

答案

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

如果您试图像在 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 及更高版本的功能,您也可以在此处使用键 / 值的确切类型)

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

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

取决于您要使用的是键还是值...

来自 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);
}

通常,在没有特定上下文的情况下询问 “最佳方法” 就像询问最佳颜色是什么?

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

另一方面,有很多方法可以迭代 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)
}

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

我会说 foreach 是标准方式,尽管它显然取决于您要查找的内容

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

那是您要找的东西吗?

您也可以在大型词典上尝试进行多线程处理。

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

我感谢这个问题已经得到了很多答复,但我想进行一些研究。

与对数组之类的对象进行迭代相比,对字典的迭代可能会比较慢。在我的测试中,数组上的迭代花费了 0.015003 秒,而字典(具有相同数量的元素)上的迭代花费了 0.0365073 秒,是它的 2.4 倍!尽管我看到了更大的差异。为了进行比较,列表在 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";
}

这个加载了键并在它们上进行了迭代(我也尝试过将键拉入 string [],但是差别可以忽略不计。

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 方法可能会做得多。

C#7.0引入了Deconstructors ,如果您使用的是.NET Core 2.0+应用程序,则结构KeyValuePair<>已经为您提供了Deconstruct() 。因此,您可以执行以下操作:

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

在此处输入图片说明

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

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

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

您还可以使用 “键和值” 集合