[C#] ¿Cómo agregar elementos a una colección mientras la consume?


Answers

Hay tres estrategias que puedes usar.

  1. Copie la Lista <> a una segunda colección (lista o matriz - quizás use ToArray ()). Pasa por esa segunda colección, añadiendo URLs a la primera.
  2. Cree una segunda lista <> y recorra su lista de urls <> agregando nuevos valores a la segunda lista. Copie esos a la lista original cuando termine de buclear.
  3. Use un bucle for en lugar de un bucle foreach . Toma tu conteo por adelantado. La lista debe dejar las cosas indexadas correctamente, por lo que debe agregar cosas que irán al final de la lista.

Prefiero # 3 ya que no tiene ninguno de los gastos generales asociados con # 1 o # 2. Aquí hay un ejemplo:

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

Editar: el último ejemplo (n. ° 3) asume que no desea procesar URL adicionales ya que se encuentran en el ciclo. Si desea procesar URL adicionales a medida que se encuentran, solo use urls.Count en el ciclo for en lugar de la variable de conteo local mencionada por el configurador en los comentarios para esta respuesta.

Question

El siguiente ejemplo arroja InvalidOperationException, "La colección fue modificada, la operación de enumeración puede no ejecutarse". al ejecutar el código.

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
}

¿Cómo puedo reescribir esto de una mejor manera? Supongo que una solución recursiva?




El enfoque de Jon es correcto; una cola es la estructura de datos correcta para este tipo de aplicación.

Suponiendo que finalmente le gustaría que su programa termine, sugeriría otras dos cosas:

  • no use string para sus URL, use System.Web.Uri : proporciona una representación de cadena canónica de la URL. Esto será útil para la segunda sugerencia, que es ...
  • ponga la representación de cadena canónica de cada URL que procesa en un diccionario. Antes de enrutar una URL, verifique primero si está en el diccionario.



alternativamente, podría tratar la colección como una cola

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



Considere usar una cola con un ciclo while (mientras q.Count> 0, url = q.Dequeue ()) en lugar de iteración.




Probablemente también pueda crear una función recursiva, como esta (no probada):

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

En este caso, no tendrá que crear dos listas, ya que GetUrl hace todo el trabajo.

Pero puede haber perdido el punto de su programa.