1

I had a complex scenario when I was using a yield with random numbers and faced some strange behavior.

I tried to simplify my case with a simpler code example and managed to reproduce it with a very simple code.

In the following code, I would expect to get "Equal!" printed, but actually, it is not.

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleAppSample
{
    static class Program
    {
        public static void Main(string[] args)
        {
            IEnumerable<int> randomNumbers = GetRandomNumbers();
            int firstRandomnumber = randomNumbers.First();

            if (firstRandomnumber == randomNumbers.First())
            {
                Console.WriteLine("Equal!");
            }
        }

        private static readonly Random random = new Random();
        private static readonly object syncLock = new object();
        public static int RandomNumber(int min, int max)
        {
            lock (syncLock)
            {
                return random.Next(min, max);
            }
        }

        private static IEnumerable<int> GetRandomNumbers(int loopCount = 2)
        {
            for (int i = 0; i < loopCount; i++)
            {
                yield return RandomNumber(int.MinValue, int.MaxValue);
            }
        }
    }
}

I tried to debug it and found even something weirder. While holding the breakpoint at the same place the values of the randomNumbers variable change every moment.

In the following image, you can see the watch shows different numbers for the same variable.

enter image description here

If I change the GetRandomNumbers() method implementation to insert values into a list and return the list (without using yield), it will work fine.

I try to understand why it works this way? I guess it is related to the way random generates numbers based on the computer clock and the way yield works, but I am not sure.

Misha Zaslavsky
  • 8,414
  • 11
  • 70
  • 116
  • 7
    `new Random().Next()` is pretty bad idea. – Sinatr Jul 07 '21 at 12:41
  • 1
    Does this answer your question? [Random number generator only generating one random number](https://stackoverflow.com/questions/767999/random-number-generator-only-generating-one-random-number) – Sinatr Jul 07 '21 at 12:42
  • I bet using a static class member `Random`, instead of instantiating in a tight loop, would `yield` a different result – maccettura Jul 07 '21 at 12:42
  • @Sinatr I agree, I just used it for the example. It doesn't matter how you generate the number the issue still happens. I will update my question. – Misha Zaslavsky Jul 07 '21 at 12:45
  • @maccettura The same issue happens with a static member. – Misha Zaslavsky Jul 07 '21 at 12:46
  • 2
    Then it's another issue, since `IEnumerable` is *lazy*. – Sinatr Jul 07 '21 at 12:46
  • [Possible duplicate](https://stackoverflow.com/q/2049806/1997232). – Sinatr Jul 07 '21 at 12:56
  • yield Enumerables aka Iterators have a 'deferred execution'. Each time the debugger fetches the data it gets different random numbers. Also when your code iterates the enumerable it executes the statement once again. Think of a method using yield return as of a Lambda expression or Func that is executed every time it Enumerator is requested. Just the same as with a LINQ statement. – lidqy Jul 07 '21 at 13:34

1 Answers1

3

yield means that the collection is not saved but essentially calculated when needed, namely each time you iterate the collection. Thus, each time new Random().Next() is called which may or may not give you a different result, depending on the time between those calls.

Thus, I would suggest the following changes:

  1. If you want to see the same collection contents everytime, save the collection (for example, into a List as you suggested)
  2. If you want to have multiple random numbers, keep the Random instance, if necessary in a static field. Creating a new instance will calculate a new seed based on the current system time and that may be the same, if you create two instances before Windows refreshes the system time.
Georg
  • 5,626
  • 1
  • 23
  • 44