c# .net linq lazy-evaluation

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.