下面的示例抛出InvalidOperationException,“Collection已被修改;枚举操作可能无法执行”。执行代码时。
var urls = new List<string>();
urls.Add("http://www.google.com");
foreach (string url in urls)
{
// Get all links from the url
List<string> newUrls = GetLinks(url);
urls.AddRange(newUrls); // <-- This is really the problematic row, adding values to the collection I'm looping
}
如何以更好的方式重写此内容?我猜一个递归的解决方案?
答案 0 :(得分:5)
var urls = new Queue<string>();
urls.Enqueue("http://www.google.com");
while(urls.Count != 0)
{
String url = url.Dequeue();
// Get all links from the url
List<string> newUrls = GetLinks(url);
foreach (string newUrl in newUrls)
{
queue.Enqueue(newUrl);
}
}
由于AddRange
中没有Queue<T>
方法,这有点难看,但我认为这基本上就是你想要的。
答案 1 :(得分:4)
您可以使用三种策略。
我更喜欢#3,因为它没有任何与#1或#2相关的开销。这是一个例子:
var urls = new List<string>();
urls.Add("http://www.google.com");
int count = urls.Count;
for (int index = 0; index < count; index++)
{
// Get all links from the url
List<string> newUrls = GetLinks(urls[index]);
urls.AddRange(newUrls);
}
编辑:最后一个示例(#3)假设您不想要处理循环中找到的其他网址。如果执行希望在找到其他网址时处理其他网址,只需在 for 循环中使用urls.Count,而不是使用本地计数变量configurator在对此答案的评论中提及。
答案 2 :(得分:2)
使用带有lambda的foreach,它更有趣!
var urls = new List<string>();
var destUrls = new List<string>();
urls.Add("http://www.google.com");
urls.ForEach(i => destUrls.Add(GetLinks(i)));
urls.AddRange(destUrls);
答案 3 :(得分:1)
我会在第二个中创建两个列表添加,然后像这样更新引用:
var urls = new List<string>();
var destUrls = new List<string>(urls);
urls.Add("http://www.google.com");
foreach (string url in urls)
{
// Get all links from the url
List<string> newUrls = GetLinks(url);
destUrls.AddRange(newUrls);
}
urls = destUrls;
答案 4 :(得分:1)
或者,您可以将集合视为队列
IList<string> urls = new List<string>();
urls.Add("http://www.google.com");
while (urls.Count > 0)
{
string url = urls[0];
urls.RemoveAt(0);
// Get all links from the url
List<string> newUrls = GetLinks(url);
urls.AddRange(newUrls);
}
答案 5 :(得分:0)
不要更改每个循环的集合。只需在列表的Count属性上使用while循环,然后按索引访问List项。这样,即使您添加项目,迭代也应该获取更改。
编辑:然后再次,这取决于你是否想要通过循环拾取你添加的新项目。如果没有,那么这将无济于事。
编辑2:我想最简单的方法就是将你的循环更改为: foreach(urls.ToArray()中的字符串url)
这将创建列表的Array副本,它将遍历此列表而不是原始列表。这将导致不循环添加的项目。
答案 6 :(得分:0)
考虑使用带有while循环的Queue(而q.Count> 0,url = q.Dequeue())而不是迭代。
答案 7 :(得分:0)
我假设您要迭代整个列表,并添加每个项目?如果是这样,我会建议递归:
var urls = new List<string>();
var turls = new List<string();
turls.Add("http://www.google.com")
iterate(turls);
function iterate(List<string> u)
{
foreach(string url in u)
{
List<string> newUrls = GetLinks(url);
urls.AddRange(newUrls);
iterate(newUrls);
}
}
答案 8 :(得分:0)
你也可以创建一个递归函数,像这样(未经测试):
IEnumerable<string> GetUrl(string url)
{
foreach(string u in GetUrl(url))
yield return u;
foreach(string ret_url in WHERE_I_GET_MY_URLS)
yield return ret_url;
}
List<string> MyEnumerateFunction()
{
return new List<string>(GetUrl("http://www.google.com"));
}
在这种情况下,您不必创建两个列表,因为GetUrl可以完成所有工作。
但我可能错过了你的节目。
答案 9 :(得分:0)
Jon的做法是正确的;队列是这种应用程序的正确数据结构。
假设您最终希望程序终止,我建议另外两件事:
string
,请使用System.Web.Uri
:它提供网址的规范字符串表示形式。这对第二个建议很有用,即...... 答案 10 :(得分:0)
如果不知道GetLinks()的作用,很难让代码更好。无论如何,这可以避免递归。标准习惯用法是当你对它进行枚举时不要改变它。虽然运行时可以让你这样做,但推理是它是错误的来源,所以最好自己创建一个新的集合或控制迭代。
public List<string> ExpandLinksOrSomething(List<string> urls)
{
List<string> result = new List<string>();
Queue<string> queue = new Queue<string>(urls);
while (queue.Any())
{
string url = queue.Dequeue();
result.Add(url);
foreach( string newResult in GetLinks(url) )
{
queue.Enqueue(newResult);
}
}
return result;
}
天真的实现假定GetLinks()
不会返回循环引用。例如A返回B,B返回A.这可以通过以下方法修复:
List<string> newItems = GetLinks(url).Except(result).ToList();
foreach( string newResult in newItems )
{
queue.Enqueue(newResult);
}
*正如其他人指出使用字典可能会更高效,具体取决于您处理的项目数量。
我觉得很奇怪,GetLinks()会返回一个值,然后再将其解析为更多的Url。也许你想做的只是一级扩展。如果是这样,我们可以完全摆脱队列。
public static List<string> StraightProcess(List<string> urls)
{
List<string> result = new List<string>();
foreach (string url in urls)
{
result.Add(url);
result.AddRange(GetLinks(url));
}
return result;
}
我决定重写它,因为虽然其他答案都使用了队列,但不明显它们没有永远运行。