Подтвердить что ты не робот

LINQ отложенное исполнение, но как?

Это должно быть что-то действительно простое. Но я все равно спрошу об этом, потому что я думаю, что другие будут бороться с этим. Почему следующий простой запрос LINQ не всегда выполняется с новым значением переменной, а не всегда с использованием первого?

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();
}

В прокомментированном примере он войдет в цикл, так как вход ABC123 содержит цифры. Но он никогда не покинет его, даже если вы введете что-то вроде ABC, так как digits все еще есть 123.

Итак, почему запрос LINQ не оценивает новое значение input, но всегда первое?

Я знаю, что могу исправить это с помощью этой дополнительной строки:

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
}

или - более элегантный - используя запрос непосредственно в цикле:

while (input.Any(Char.IsDigit))
{
    // ...
}
4b9b3361

Ответ 1

Разница в том, что вы меняете значение переменной input, а не на содержимое объекта, к которому относится переменная... так что digits все еще относится к исходной коллекции.

Сравните это с этим кодом:

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());
}

На этот раз мы модифицируем содержимое коллекции, к которой относится input - и поскольку digits является фактически представлением над этой коллекцией, мы видим это изменение.

Ответ 2

Вы назначаете новое значение input, но последовательность digits по-прежнему выводится из начального значения input. Другими словами, когда вы делаете digits = input.Where(Char.IsDigit), , он фиксирует текущее значение переменной input, а не самой переменной. Назначение нового значения input не влияет на digits.

Ответ 3

Эта строка:

input.Where(Char.IsDigit)

эквивалентно:

Enumerable.Where(input, Char.IsDigit)

Таким образом, значение input передается как источник запроса .Where, а не ссылка на input.

Первое исправленное вами предложение работает, потому что оно использует только что присвоенное значение input в предыдущей строке.

Ответ 4

Перечисляемые цифры относятся к копии строки, которая input содержится при создании перечислимого. Он не содержит ссылки на переменную input, а изменение значения, хранящегося в input, не приведет к тому, что перечисление перечислимого типа будет использоваться для использования нового значения.

Помните, что Where является статическим методом расширения и принимает объект, который вы вызываете его как параметр.

Ответ 5

Это почти комментарий, но содержит структурированный код, поэтому я отправляю его как ответ.

Следующая небольшая модификация вашего кода будет работать:

  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();

Здесь input захватывается (закрывается) делегатом типа Func<bool>.

Ответ 6

Я отвечаю только, чтобы добавить точность к другим хорошим ответам, об отложенном выполнении.

Даже если запрос LINQ еще не был оценен (используя .Any()), запрос всегда всегда ссылается на начальное содержимое переменной. Даже если запрос LINQ оценивается после, то что-то новое повлияло на переменную, исходное содержимое не изменяется, а отложенное выполнение будет использовать исходное содержимое, к которому всегда обращался запрос:

var input = "ABC123";
var digits = input.Where(Char.IsDigit);
input = "NO DIGIT";
var result = digits.ToList();   // 3 items