Sí, yield return
hace una forma de continuación. Aunque para muchos casos útiles, Linq proporciona operadores funcionales que le permiten conectar un generador de secuencia perezosa, de hecho, en C# 3 no es necesario usar tanto yield return
(excepto cuando agrega más extensiones de estilo Linq propias a tapar espacios en la biblioteca, p. ej. Zip, Desplegar).
En el ejemplo, factorizamos un número entero por fuerza bruta. Esencialmente el mismo ejemplo en C# se puede hacer con la incorporada en los operadores LINQ:
var factors = Enumerable.Range(2, 100)
.Join(Enumerable.Range(2, 100),
n => 1, n => 1, (i, j) => new { i, j })
.First(v => v.i*v.j == 481);
Console.WriteLine("Factors are " + factors.i + ", " + factors.j);
Aquí los puntos de partida son mis dos llamadas a Enumerable.Range
, que está incorporado a LINQ pero se puede aplicar a sí mismo como:
IEnumerable<int> Range(int start, int stop)
{
for (int n = start; n < stop; n++)
yield return n;
}
Hay dos parámetros impares, el n => 1
, n => 1
parámetros a Join
. Estoy seleccionando 1 como el valor de clave para Join
para usar al hacer coincidir elementos, por lo tanto, todas las combinaciones coincidirán y, por lo tanto, puedo probar cada combinación de números de los rangos.
Entonces se enciende el par de valores en una especie de tupla (un tipo anónimo) con:
(i, j) => new { i, j })
Por último, tomo la primera tupla para el que se cumple mi prueba:
.First(v => v.i*v.j == 481);
actualización
el código dentro de la llamada a First
no necesita ser simplemente una expresión de la prueba corta. Puede ser un montón de código imperativo que debe ser "reiniciado" si no pasa la prueba:
.First(v =>
{
Console.WriteLine("Aren't lambdas powerful things?");
return v.i*v.j == 481;
);
Así que la parte del programa que potencialmente se debe reiniciar con diferentes valores que va en lambda. Siempre que esa lambda quiera reiniciarse con valores diferentes, simplemente devuelve falso, el equivalente a llamar al amb
sin argumentos.
código Niza - Pero tiene un efecto muy diferente a la AMB. Amb elige su valor de modo que no falle el código completo, no solo el siguiente cálculo. – Dario
El "reinicio" debe limitarse a un contexto particular, sin embargo se implementa; específicamente, sería una tontería reiniciar todo el programa, ¡incluyendo todo el trabajo realizado antes de que se introdujeran las variables amb! En mi versión, esto se pone de manifiesto canalizando las variables amb en una secuencia, y luego el contexto reiniciable es la lambda pasada a Primero - vea la actualización anterior que puede aclarar esto. El código en el lambda puede ser arbitrariamente complicado. –
De acuerdo, buen código, pero esta es una implementación de fuerza bruta determinista. Esta no es una implementación simultánea no determinista, que creo que son dos de los atributos clave de la implementación de un operador AMB. Como nota al margen, desde esta publicación, Rx fue lanzado y tiene un operador Amb. –