c# - La ejecución diferida de LINQ, pero ¿cómo?
.net lazy-evaluation (6)
Está asignando un nuevo valor a la input
, pero la secuencia de digits
aún se deriva del valor inicial de la input
. En otras palabras, cuando haces digits = input.Where(Char.IsDigit)
, captura el valor actual de la variable de input
, no la variable en sí. Asignar un nuevo valor a la input
no tiene efecto en los digits
.
Esto debe ser algo realmente simple. Pero lo voy a preguntar de todos modos, porque creo que otros también lucharán con eso. ¿Por qué la siguiente consulta LINQ simple no se ejecuta siempre con el nuevo valor variable en lugar de usar siempre el primero?
static void Main(string[] args)
{
Console.WriteLine("Enter something:");
string input = Console.ReadLine(); // for example ABC123
var digits = input.Where(Char.IsDigit); // 123
while (digits.Any())
{
Console.WriteLine("Enter a string which doesn''t contain digits");
input = Console.ReadLine(); // for example ABC
}
Console.WriteLine("Bye");
Console.ReadLine();
}
En la muestra comentada entrará en el bucle ya que la entrada ABC123
contiene dígitos. Pero nunca lo dejará, incluso si ingresa algo como ABC
ya que los digits
aún son 123
.
Entonces, ¿por qué la consulta LINQ no evalúa el nuevo valor de input
sino siempre el primero?
Sé que podría arreglarlo con esta línea adicional:
while (digits.Any())
{
Console.WriteLine("Enter a string which doesn''t contain digits");
input = Console.ReadLine();
digits = input.Where(Char.IsDigit); // now it works as expected
}
o - más elegante - usando la consulta directamente en el bucle:
while (input.Any(Char.IsDigit))
{
// ...
}
Esta línea:
input.Where(Char.IsDigit)
es equivalente a:
Enumerable.Where(input, Char.IsDigit)
Por lo tanto, el valor de la input
se pasa como la fuente de la consulta .Where
, no una referencia a la input
.
La primera solución que propuso funciona porque usa el valor de input
recién asignado en la línea anterior.
Esto es casi un comentario, pero contiene código estructurado, así que lo envío como respuesta.
La siguiente ligera modificación de tu código funcionará:
Console.WriteLine("Enter something:");
string input = Console.ReadLine(); // for example ABC123
Func<bool> anyDigits = () => input.Any(Char.IsDigit); // will capture ''input'' as a field
while (anyDigits())
{
Console.WriteLine("Enter a string which doesn''t contain digits");
input = Console.ReadLine(); // for example ABC
}
Console.WriteLine("Bye");
Console.ReadLine();
Aquí la input
es capturada (cierre) por el delegado de tipo Func<bool>
.
Estoy respondiendo solo para agregar una precisión a las otras buenas respuestas, sobre la ejecución diferida .
Incluso si la consulta LINQ aún no se ha evaluado (utilizando .Any()
), la consulta interna siempre se refiere al contenido inicial de la variable . Incluso si la consulta LINQ se evalúa después de que algo nuevo haya sido afectado a la variable, el contenido inicial no cambia y la ejecución diferida usará el contenido inicial al que la consulta siempre se ha referido:
var input = "ABC123";
var digits = input.Where(Char.IsDigit);
input = "NO DIGIT";
var result = digits.ToList(); // 3 items
La diferencia es que está cambiando el valor de la variable de input
, en lugar del contenido del objeto al que se refiere la variable ... por lo que los digits
aún se refieren a la colección original.
Compare eso con este código:
List<char> input = new List<char>(Console.ReadLine());
var digits = input.Where(Char.IsDigit); // 123
while (digits.Any())
{
Console.WriteLine("Enter a string which doesn''t contain digits");
input.Clear();
input.AddRange(Console.ReadLine());
}
Esta vez, estamos modificando el contenido de la colección a la que se refiere la input
, y como los digits
son efectivamente una vista sobre esa colección, podemos ver el cambio.
Los dígitos enumerables se refieren a una copia de la cadena que contenía la input
cuando creó el enumerable. No contiene una referencia a la variable de input
, y cambiar el valor almacenado en la input
no causará materializaciones de los enumerables para usar el nuevo valor.
Recuerde que Where
es un método de extensión estática y acepta el objeto en el que lo está invocando como parámetro.