Para empezar diré que estoy de acuerdo en que las declaraciones goto son irrelevantes en construcciones de alto nivel en los lenguajes de programación modernos y no deberían usarse cuando un sustituto adecuado es disponible.Otras formas de lidiar con la "inicialización de bucle" en C#
Estaba volviendo a leer una edición original del Código Completo de Steve McConnell recientemente y había olvidado su sugerencia de un problema de codificación común. Lo había leído hace años cuando comencé y no creo haberme dado cuenta de lo útil que sería la receta. El problema de codificación es el siguiente: al ejecutar un bucle, a menudo es necesario ejecutar parte del bucle para inicializar el estado y luego ejecutar el bucle con otra lógica y finalizar cada bucle con la misma lógica de inicialización. Un ejemplo concreto es implementar el método String.Join (delimitador, matriz).
Creo que la primera opinión de todos sobre el problema es esta. Supongamos que el método de agregar está definido para agregar el argumento a su valor de retorno.
bool isFirst = true;
foreach (var element in array)
{
if (!isFirst)
{
append(delimiter);
}
else
{
isFirst = false;
}
append(element);
}
Nota: Poca optimización para esto es para quitar el otro y ponerlo al final del bucle. Una asignación que generalmente es una instrucción única y equivalente a otro y disminuye el número de bloques básicos en 1 y aumenta el tamaño de bloque básico de la parte principal. El resultado es que ejecuta una condición en cada ciclo para determinar si debe agregar el delimitador o no.
También he visto y utilizado otras tomas para resolver este problema común de bucle. Puede ejecutar el código de elemento inicial primero fuera del bucle, luego realice su bucle desde el segundo elemento hasta el final. También puede cambiar la lógica para agregar siempre el elemento, luego el delimitador y una vez que se complete el ciclo, simplemente puede eliminar el último delimitador que agregó.
La última solución tiende a ser la que prefiero solo porque no duplica ningún código. Si alguna vez cambia la lógica de la secuencia de inicialización, no tiene que recordar fijarla en dos lugares. Sin embargo, requiere un "trabajo" adicional para hacer algo y luego deshacerlo, causando al menos ciclos de CPU adicionales y en muchos casos, como nuestro ejemplo de String.Join también requiere memoria extra.
que estaba emocionado luego de leer esta construcción
var enumerator = array.GetEnumerator();
if (enumerator.MoveNext())
{
goto start;
do {
append(delimiter);
start:
append(enumerator.Current);
} while (enumerator.MoveNext());
}
La ventaja aquí es que no se obtiene ningún código duplicado y se obtiene ningún trabajo adicional. Comienza su ciclo a la mitad de la ejecución de su primer ciclo y esa es su inicialización. Está limitado a simular otros bucles con el constructo do while, pero la traducción es fácil y leerla no es difícil.
Entonces, ahora la pregunta. Felizmente fui a intentar agregar esto a un código en el que estaba trabajando y descubrí que no funcionaba. Funciona muy bien en C, C++, Básico, pero resulta que en C# no se puede saltar a una etiqueta dentro de un alcance léxico diferente que no sea un alcance principal. Yo estaba muy decepcionado. Entonces me quedé pensando, ¿cuál es la mejor manera de lidiar con este problema de codificación muy común (lo veo principalmente en la generación de cadenas) en C#?
quizá Para ser más específicos con los requisitos:
- No se debe copiar el código
- No hacer trabajo innecesario
- No tenga más de 2 o 3 veces más lento que otro código
- ser legible
Creo que la lectura es la única cosa que podría posiblemente sufrir con la receta he dicho. Sin embargo, no funciona en C#, entonces, ¿cuál es la siguiente mejor opción?
* Editar * Cambié mis criterios de rendimiento debido a algo de la discusión. El rendimiento generalmente no es un factor limitante aquí, por lo que el objetivo más correcto debería ser no ser irracional, no ser el más rápido.
La razón por la que no me gustan las implementaciones alternativas que sugiero es porque duplican código que deja espacio para cambiar una parte y no la otra o para la que generalmente elijo requiere "deshacer" la operación que requiere pensamiento y tiempo extra para deshacer lo que acabas de hacer Con la manipulación de cadenas en particular, esto generalmente te deja abierto por un error o por no dar cuenta de una matriz vacía e intentar deshacer algo que no sucedió.
Pero una vez que sabes de string.join, ¿con qué frecuencia usted se encuentra realmente escribir dichos bucles más? –
Encuentro todo tipo de razones para no usarlo. Actualmente estoy trabajando en algo que hace mucha manipulación de cadenas con un árbol de objetos. Tengo que pasar un generador de cadenas para no tener muchas asignaciones innecesarias a medida que traduzco un gráfico de árbol arbitrario en una cadena. No es posible usar esta sobrecarga porque no existe para el generador de cadenas, y aunque lo hiciera, no podría usar algo como sb.Join ("delim", Children.Select (child => child.Build (sb)) porque los hijos se agregarían al generador de cuerdas en el orden incorrecto. –
@ Peter Oehlert: Puede escribir una función que devuelva IEnumerable, luego use yield return para devolver los resultados en el orden que desee. –