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

Jake Stewart 2455 c# dictionary loops

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

Pablo Fernan.. 3493

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

  • @MonkeyWrench:嗯.Visual Studio知道类型是什么; 所有你需要做的就是将鼠标悬停在变量上以查找. (101认同)
  • @OzairKafray使用`var`时你不知道类型通常是不好的做法. (83认同)
  • 这个答案是优越的,因为Pablo没有默认使用混淆返回类型的懒惰编码器"var"用法. (45认同)
  • 据我所知,`var`只有在编译时知道类型才有效.如果Visual Studio知道该类型,那么您也可以找到它. (26认同)
  • 如果我不完全知道Dictionary中键/值的类型该怎么办?在这种情况下使用`var entry`会更好,因此我在第二次看而不是上面的那一次投票[这个回答](http://stackoverflow.com/a/141105/365188). (20认同)
  • @Gusdor我们正在接受一场神圣的战争;).老实说,我不想知道或考虑类型是什么.我只是想知道它是用户的一些表示.无论如何,选择是你和我的.我发现`var`更具可读性. (10认同)
  • @Gusdor`var`对运行时行为没有任何影响.是的,你疯了:) <3 (7认同)
  • 你应该总是在`foreach`语句中使用`var`的一个重要原因是,如果你不这样做,很难看出语法中是否隐藏了一个显式的强制转换,一个可能失败的强制转换!当编译时类型的`myDic`是一个通用的`Dictionary <,>`时,这不会发生,因为`Current`的编译时类型是一个不允许这样的强制转换的结构.但设置`IDictionary myDic2 = myDic;`,然后`foreach(myDic2中的KeyValuePair <string,string>条目)`将编译,但会在运行时抛出(除非`myDic`为空).另外:`IList <IAnimal> s = ...; foreach(IPlant p in s){}` (6认同)
  • @Gusdor _Consider`var userInfo = service.GetUser();`.什么类型是`userInfo`?_ - 答案是,我什么时候关心?当我在Visual Studio中编写代码时,我需要知道从类型中可用的属性和方法.在GitHub上,我可能应该避免在没有VS,编译器和Intellisense安全网的情况下编辑代码.为了阅读GitHub上的代码,我认为坚持只将编译代码同步到GitHub是一种更好的做法,因此我从周围代码使用其属性和方法获得了关于`userInfo`的足够信息...... (6认同)
  • Gee whiz,人们......"var"在编译时编译为实际类型 - 它只是为了编码器的方便.这样,如果您需要更改类型,那么您需要更少的地方. (5认同)
  • 我为只使用var来执行linq查询,匿名类型和以构造函数调用结尾的赋值而感到疯狂吗?最大化可读性,避免运行时的副作用。 (3认同)
  • @Gusdor这不是一个突破性的变化吗?我希望"外部库"的作者有足够的责任在发行说明中描述它,所以我可以在我的代码中找到所有对"GetItems"的引用并相应地进行更改.如果作者没有足够的责任去做,那么现在是时候找到一个新的"外部图书馆"了. (3认同)
  • @Gusdor`var`通常更多地是关于可读性而不是可写性.当我阅读一种方法时,我想从高层次的理解开始.然后我会在必要时深入细节.在您的示例中,返回类型的更改并不意味着底层实现是惰性的.无论如何,这样的API很难处理,因为已经不清楚谁负责处理流.无论如何,我并不是说永远不要使用明确的类型,我说我更喜欢`var`几乎无处不在.当我必须_read_它时,Java让我的眼睛流血,而不仅仅是_write_它. (2认同)
  • ...即使我不知道确切的类型,也可以很好地了解它是什么。在这种情况下,实际上使用具体的类型名称会掩盖代码的意图-“ userInfo”是“ MyCompany.MyProject.InnerNamespace.UserDetails”的实例;无需浏览到适当的文件(并且在GitHub上我没有Goto Reference,因此我将不得不手动找到它)实际上没有添加任何信息;因此,该名称成为我在阅读时必须分析的另一点杂音。只是我的2美分。 (2认同)

Jacob.. 815

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

  • 我很欣赏这个答案指出你可以明确地迭代键或值. (26认同)
  • 我不喜欢在这里使用var.鉴于它只是语法糖,为什么在这里使用它?当有人试图阅读代码时,他们必须跳过代码来确定`myDictionary`的类型(除非那是当然的实际名称).我认为当类型很明显时使用var是好的,例如`var x ="some string"`但是当它不是很明显时我觉得它是懒惰的编码会伤害代码阅读器/审阅者 (10认同)
  • 你的第一个代码块最需要var功能:) (9认同)
  • 在我看来,`var`应该谨慎使用.特别是在这里,它不具有建设性:"KeyValuePair"类型可能与问题相关. (6认同)
  • `var`有一个独特的目的,我不相信它是'语法'糖.有目的地使用它是一种合适的方法. (2认同)

小智.. 141

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

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

  • 这个答案完全不值得这么多的赞成.字典没有隐式顺序,因此在此上下文中使用".ElementAt"可能会导致细微的错误.更重要的是阿图罗的观点.你将迭代字典`dictionary.Count + 1`次,导致O(n ^ 2)复杂度的操作应该只是O(n).如果你真的需要一个索引(如果你这样做,你可能首先使用了错误的集合类型),你应该迭代`dictionary.Select((kvp,idx)=> new {Index = idx,kvp.Key ,kvp.Value})`而不是在循环中使用`.ElementAt`. (154认同)
  • 不是'ElementAt`是O(n)操作吗? (26认同)
  • 要使用'.ElementAt'方法,请记住:using System.Linq; 这不包括在fx中.自动生成的测试类. (21认同)
  • 如果要修改与键关联的值,则可以使用此方法.否则在修改和使用foreach()时会抛出异常. (13认同)
  • 使用时要小心.请参见此处:http://stackoverflow.com/a/2254480/253938 (8认同)
  • ElementAt - o(n)操作!真的吗?这是你不应该怎么做的例子.这些赞成票? (4认同)
  • 我希望对于大型词典来说这会非常慢. (3认同)
  • O(n ^ 2)复杂度将使大型集合的速度变慢 (3认同)

J Healy.. 95

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

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


Stéphane Gou.. 79

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

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

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

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


George Mauer.. 48

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

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

这就是你要找的东西吗?

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

Onur.. 37

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

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

  • @WiiMaxx,如果这些项目不相互依赖,则更为重要 (6认同)

Liath.. 28

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

与迭代类似数组的东西相比,迭代字典可能会相当慢.在我的测试中,对数组的迭代花费了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方法要差很多.

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

theo.. 26

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

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

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

您还可以使用键和值集合


Jaider.. 26

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

  • 如果使用的.NET Framework至少在4.7.2或更高版本的KeyValuePair上没有解构,请尝试以下操作:`dic.Select(x =&gt;(x.Key,x。)中的foreach(var(key,value))。值)))` (4认同)

Pavel.. 12

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

  • 这是不正确的..NET 4.7内置了`ValueTuple`.它可以作为早期版本的nuget包使用.更重要的是,"Deconstruct"方法需要C#7.0+作为水果中`var(水果,数字)的解构函数. (7认同)

小智.. 11

您建议在下面进行迭代

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类型,则FYI 不起作用.

  • 请详细说明:如果_which_值是`object`类型,`foreach`将不起作用?否则这没有多大意义. (4认同)

Ron.. 9

迭代字典的最简单形式:

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


sɐunıɔןɐqɐp.. 8

使用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'
}


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

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

tupleKeyValuePair直接使用创建一个扩展方法相比,只需要很少的代价来调用扩展方法,如果你要分配KeyValuePair的属性KeyValue新的循环变量,这应该不是问题.

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

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

  • 嗨Maarten,谢谢你的问题.主要的好处是代码可读性,无需额外的编程工作.使用KeyValuePair时,必须始终使用`kvp.Key`和`kvp.Value`形式分别使用键和值.使用元组,您可以灵活地根据需要命名键和值,而无需在foreach块中使用进一步的变量声明.例如,您可以将您的密钥命名为`factoryName`,将值命名为`models`,这在您获得嵌套循环(字典字典)时特别有用:代码维护变得更加容易.试一试吧!;-) (4认同)

ender.. 6

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

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


小智.. 5

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

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

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

  • 这看起来像使用非泛型版本的Dictionary ...即在.NET framework 2.0之前. (3认同)

归档时间:

查看次数:

1481345 次

最近记录:

7 月,2 周 前