¿Cuál es la mejor manera de terminar el ciclo de lectura?

13

Cuando tienes que iterar en un lector donde se desconoce la cantidad de elementos para leer, y la única manera de hacerlo es seguir leyendo hasta que llegues al final.

Este es a menudo el lugar donde necesitas un bucle sin fin.

  1. Hay siempre el true que indica que debe haber una declaración break o return en algún lugar dentro del bloque.

    int offset = 0;
    while(true)
    {
        Record r = Read(offset);
        if(r == null)
        {
            break;
        }
        // do work
        offset++;
    }
    
  2. Existe el doble leído para el método de bucle.

    Record r = Read(0);
    for(int offset = 0; r != null; offset++)
    {
        r = Read(offset);
        if(r != null)
        {
            // do work
        }
    }
    
  3. Hay un solo bucle while de lectura. No todos los idiomas admiten este método .

    int offset = 0;
    Record r = null;
    while((r = Read(++offset)) != null)
    {
        // do work
    }
    

Me pregunto qué enfoque es el menos probable para introducir un error, el más legible y el más utilizado.

Cada vez que tengo que escribir uno de estos, creo que "tiene que haber una mejor manera" .

    
pregunta cgTag 25.12.2013 - 18:08

4 respuestas

49

Daría un paso atrás aquí. Te estás concentrando en los detalles delicados del código, pero te estás perdiendo la imagen más grande. Echemos un vistazo a uno de sus bucles de ejemplo:

int offset = 0;
while(true)
{
    Record r = Read(offset);
    if(r == null)
    {
        break;
    }
    // do work
    offset++;
}

¿Qué significa significado de este código? El significado es "hacer algo de trabajo para cada registro en un archivo". Pero no es así como se ve el código . El código parece "mantener un desplazamiento. Abrir un archivo. Ingresar un bucle sin condición de finalización. Leer un registro. Comprobar la nulidad". ¡Todo eso antes de que lleguemos al trabajo! La pregunta que debe hacerse es " ¿cómo puedo hacer que la apariencia de este código coincida con su semántica? " Este código debería ser:

foreach(Record record in RecordsFromFile())
    DoWork(record);

Ahora el código se lee como su intención. Separa tus mecanismos de tu semántica . En su código original, usted mezcla el mecanismo (los detalles del bucle) con la semántica, el trabajo realizado en cada registro.

Ahora tenemos que implementar RecordsFromFile() . ¿Cuál es la mejor manera de implementar eso? ¿A quién le importa? Ese no es el código que alguien verá. Es un código de mecanismo básico y sus diez líneas de largo. Escríbelo como quieras. ¿Qué tal esto?

public IEnumerable<Record> RecordsFromFile()
{
    int offset = 0;
    while(true)
    {
        Record record = Read(offset);
        if (record == null) yield break;
        yield return record;
        offset += 1;
    }
}

Ahora que estamos manipulando una secuencia de registros calculada perezosamente , todo tipo de escenarios son posibles:

foreach(Record record in RecordsFromFile().Take(10))
    DoWork(record);

foreach(Record record in RecordsFromFile().OrderBy(r=>r.LastName))
    DoWork(record);

foreach(Record record in RecordsFromFile().Where(r=>r.City == "London")
    DoWork(record);

Y así sucesivamente.

Cada vez que escriba un bucle, pregúntese "¿este bucle se lee como un mecanismo o como el significado del código?" Si la respuesta es "como un mecanismo", intente mover ese mecanismo a su propio método y escriba el código para que el significado sea más visible.

    
respondido por el Eric Lippert 26.12.2013 - 23:01
19

No necesitas un bucle sin fin. Nunca deberías necesitar uno en los escenarios de lectura de C #. Este es mi enfoque preferido, asumiendo que realmente necesita mantener un desplazamiento:

Record r = Read(0);
offset=1;
while(r != null)
{
    // Do work
    r = Read(offset);
    offset++
}

Este enfoque reconoce el hecho de que hay un paso de configuración para el lector, por lo que hay dos llamadas de método de lectura. La condición while está en la parte superior del bucle, en caso de que no haya datos en absoluto en el lector.

    
respondido por el Robert Harvey 25.12.2013 - 18:15
6

Bueno, depende de tu situación. Pero una de las más soluciones "C # -ish" que se me ocurren es usar la interfaz IEnumerable integrada y un bucle foreach. La interfaz para IEnumerator solo muestra MoveNext con verdadero o falso, por lo que el tamaño puede ser desconocido. Luego, su lógica de terminación se escribe una vez, en el enumerador, y no tiene que repetir en más de un lugar.

MSDN proporciona un ejemplo de IEnumerator < T > . También deberá crear un IEnumerable < T > para devolver el IEnumerator < T & gt ;.

    
respondido por el J Trana 25.12.2013 - 21:56
2

Cuando tengo operaciones de inicialización, condición e incremento, me gusta usar los bucles for de lenguajes como C, C ++ y C #. Así:

for (int offset = 0, Record r = Read(offset); r != null; r = Read(++offset)){
    // loop here!
}

O esto si crees que esto es más legible. Personalmente prefiero el primero.

for (int offset = 0, Record r = Read(offset); r != null; offset++, r = Read(offset)){
    // loop here!
}
    
respondido por el Trylks 26.12.2013 - 03:34

Lea otras preguntas en las etiquetas