关于c#:while循环中的字典并删除字典中的项目


dictionary in while loop and removing items in dictionary

我有一个字典,如果字典中的项目通过所有的处理,我想删除它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
            var dictEnum = dictObj.GetEnumerator();
            while (dictEnum.MoveNext())
            {
                 Parallel.ForEach(dictObj, pOpt, (KVP, loopState) =>
                 {
                      processAndRemove(KVP.Key);
                 });
            }

            private void processAndRemove(string keyId)
            {
               try
               {
               <does stuff>
               dictObj.Remove(keyId);
               } catch(exception ex) {
                 ...
                 <does not remove anything, wants to retry until it doesn't fail>
               }
            }

我希望循环继续处理字典中的所有剩余项(未删除)。

但是,我得到了一个错误。当我运行此代码的更简单版本时,会收到一条消息,说明:

Collection was modified; enumeration operation may not execute

有没有办法用字典来做这个?

更新:

只是为了提供更多的上下文。这背后的想法是,如果dictobj中还有项目,那么循环将继续运行。所以如果我从10和8开始,我想重新运行2,直到他们通过。


正如Jalayn所说,在枚举集合时不能将其从集合中移除。您必须重写代码,以便它添加到另一个集合中,然后枚举该集合并从原始集合中删除这些项。

比如:

1
2
3
4
5
6
7
8
9
10
11
var toRemove = new Dictionary<int, string>() //whatever type it is

Parallel.ForEach(dictObj, pOpt, (KVP, loopState) =>
{
    toRemove.Add(KVP);
});

foreach (var item in toRemove)
{
    dictObject.Remove(item.Key);
}


如果同时对某个集合进行迭代,则无法从该集合中移除该项。但是,您可以将要删除的所有元素存储在单独的集合中。

然后,枚举完之后,可以遍历列表以从原始集合中删除每个项。

或者,检查从C字典中删除与谓词匹配的多个项的最佳方法?很漂亮。由@jaredpar用户提供的公认答案摘录是:

1
2
3
foreach ( var s in MyCollection.Where(p => p.Value.Member == foo).ToList() ) {
  MyCollection.Remove(s.Key);
}


从我和杰佩·斯蒂格·尼尔森的谈话中,我产生了一个想法,想试试埃多克斯1号(8号)。

这是我的测试代码,我可以从字典中删除项目(在parallel.foreach循环中),并且while循环一直持续到count == 0 or the retryAttempts > 5循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
    public static ConcurrentDictionary<string, myRule> ccDict= new ConcurrentDictionary<string, myRule>();
       try
        {
            while (ccDict.Count > 0)
            {
                Parallel.ForEach(ccDict, pOptions, (KVP, loopState) =>
                {
                    //This is the flag that tells the loop do exit out of loop if a cancellation has been requested
                    pOptions.CancellationToken.ThrowIfCancellationRequested();
                    processRule(KVP.Key, KVP.Value, loopState);
                }); //End of Parallel.ForEach loop
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message.ToString());
            Console.ReadLine();
        }

    public static int processRule(string rId, myRule rule, ParallelLoopState loopState)
    {
        try
        {
            if (rId =="001" || rId =="002")
            {
                if (rId =="001" && ccDict[rId].RetryAttempts == 2)
                {
                    operationPassed(rId);
                    return 0;
                }
                operationFailed(rId);
            }
            else
            {
                operationPassed(rId);
            }
            return 0;
        }
        catch (Exception ex)
        {
            Console.WriteLine("failed :" + ex.Message.ToString());
            return -99;
        }
    }

    private static void operationPassed(string rId)
    {
        //Normal Operation
        ccDict[rId].RulePassed = true;
        ccDict[rId].ExceptionMessage ="";
        ccDict[rId].ReturnCode = 0;

        Console.WriteLine("passed:" + rId +" Retry Attempts :" + ccDict[rId].RetryAttempts.ToString());

        rule value;
        ccDict.TryRemove(rId, out value);
    }

    private static void operationFailed(string ruleId)
    {
        //This acts as if an EXCEPTION has OCCURED
        int retryCount = 0;

            ccDict[rId].RulePassed = false;
            ccDict[rId].RetryAttempts = ccDict[rId].RetryAttempts + 1;
            ccDict[rId].ExceptionMessage ="Forced Fail";
            ccDict[rId].ReturnCode = -99;

            ccDict.TryUpdate(rId, ccDict[rId], ccDict[rId]);

            if (ccDict[rId].RetryAttempts >= 5)
            {
                Console.WriteLine("Failed:" + rId +" Retry Attempts :" + ccDict[rId].RetryAttempts.ToString() +" :" + ccDict[rId].ExceptionMessage.ToString());
                cancelToken.Cancel();
            }
    }

    public class myRule
    {
        public Boolean RulePassed = true;
        public string ExceptionMessage ="";
        public int RetryAttempts = 0;
        public int ReturnCode = 0;


        public myRule()
        {
            RulePassed = false;
            ExceptionMessage ="";
            RetryAttempts = 0;
            ReturnCode = 0;
        }
    }

为什么要明确地调用GetEnumerator(),而不是使用foreach?有foreach声明可以帮助你。在这种情况下,您在循环中使用MoveNext(),但从未读取Current属性。

看起来您试图在您的dictObj上使用Parallel.ForEach,但是您确定它是线程安全的类型吗?可能不会。它是什么类型的?

最后,错误文本本身就说明了问题。不能修改正在迭代的同一集合。


启动第二个集合,并向其中添加要保留的值。


我觉得你用字典是办不到的。相反,你可以做一些类似于Dictionary.Values.ToList()的事情,去掉你想要的,然后调和差异。

此问题包含有关已修改的IT集合的详细信息;枚举操作可能无法执行