Record the history of yield return ., Yieldreturn
The process is as follows:
I used C # To write a simple function that generates sequences through iteration.
public static IEnumerable<T> Iterate<T>(this Func<T, T> f, T initVal, int length){ Checker.NullCheck(nameof(f), f); Checker.RangeCheck(nameof(length), length, 0, int.MaxValue); var current = initVal; while (--length >= 0) { yield return (current = f(current)); }}
NullCheck is used to check whether the parameter is null. If yes, an ArgumentNullException exception is thrown.
Correspondingly, I wrote the following unit test code to detect this exception.
public void TestIterate(){ Func<int, int> f = null; Assert.Throws<ArgumentNullException>(() => f.Iterate(1, 7)); // Other tests}
However, this test was unexpectedly fail.
At first, I thought it was a problem with the NullCheck function, but I changed the NullCheck directly to the if statement.
Then I breakpoint and debug the Iterate function. Result The debugger did not stop at the breakpoint and ran the test directly.
I thought the method I tested was wrong, so I constantly modified the test code, and even thought it was a bug in. NET Unit Tests.
Finally, I found the problem in this test code:
Assert.Throws<ArgumentNullException>(() =>{ var seq = f.Iterate(1, 7); foreach (int ele in seq) Console.WriteLine(ele);});
When I debug this test, the program stops at the break point of my previous Iterate function.
As a result, I break a breakpoint on var seq = f. Iterate (1, 7); and run it gradually. At this time, I found that when the program runs var seq = f. Iterate (1, 7); it does not enter the Iterate function; instead, it only enters after the program runs the foreach statement.
This involves the specific workflow of yield return. When yield return appears in the function code, calling this function will return an IEnumerable <T> or IEnumerator <T> object, but will not execute any code in the function body. The function is executed only when you execute the IEnumerator <T> MoveNext () function of the returned IEnumerator <T> object obtained by calling the GetEnumerator method of the returned object.
Therefore, the preceding two checks are not executed during function calling, but are executed only when you start foreach.
This is not the result I want. I want to check the validity of the parameter when calling the function. If it is invalid, an exception is thrown directly.
There are two ways to solve this problem. One is to split it into two functions:
public static IEnumerable<T> Iterate<T>(this Func<T, T> f, T initVal, int length){ Checker.NullCheck(nameof(f), f); Checker.RangeCheck(nameof(length), length, 0, int.MaxValue); return IterateWithoutCheck(f, initVal, length);}private static IEnumerable<T> IterateWithoutCheck<T>(this Func<T, T> f, T initVal, int length){ var current = initVal; while (--length >= 0) { yield return (current = f(current)); }}
Alternatively, you can wrap this function into a class.
class FunctionIterator<T> : IEnumerable<T> { private readonly Func<T, T> f; private readonly T initVal; private readonly int length; public FunctionIterator(Func<T, T> f, T initVal, int length) { Checker.NullCheck(nameof(f), f); Checker.RangeCheck(nameof(length), length, 0, int.MaxValue); this.f = f; this.initVal = initVal; this.length = length; } public IEnumerator<T> GetEnumerator() { T current = initVal; for (int i = 0; i < length; ++i) yield return (current = f(current)); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } }