为什么使用"yield"延迟执行会有不同的运行时行为?c#中的关键字

本文关键字:quot 运行时 关键字 yield 延迟 执行 为什么 | 更新日期: 2023-09-27 17:53:21

如果您在下面的示例代码中调用IgnoreNullItems扩展方法,延迟执行会像预期的那样工作,但是当使用IgnoreNullItemsHavingDifferentBehaviour时,异常会立即引发。为什么?

List<string> testList = null;
testList.IgnoreNullItems(); //nothing happens as expected
testList.IgnoreNullItems().FirstOrDefault();
//raises ArgumentNullException as expected
testList.IgnoreNullItemsHavingDifferentBehaviour(); 
//raises ArgumentNullException immediately. not expected behaviour -> 
//  why is deferred execution not working here?

感谢分享你的想法!

Raffael Zaghet

public static class EnumerableOfTExtension
{
    public static IEnumerable<T> IgnoreNullItems<T>(this IEnumerable<T> source)
        where T: class
    {
        if (source == null) throw new ArgumentNullException("source");
        foreach (var item in source)
        {
            if (item != null)
            {
                yield return item;
            }
        }
        yield break;
    }
    public static IEnumerable<T> IgnoreNullItemsHavingDifferentBehaviour<T>(
        this IEnumerable<T> source) 
        where T : class
    {
        if (source == null) throw new ArgumentNullException("source");
        return IgnoreNulls(source);
    }
    private static IEnumerable<T> IgnoreNulls<T>(IEnumerable<T> source)
        where T : class
    {
        foreach (var item in source)
        {
            if (item != null)
            {
                yield return item;
            }
        }
        yield break;
    }
}

这里有一个具有相同行为的版本:

这里是显示相同行为的版本。在这种情况下,不要让resharper"改进"你的foreach语句;)——> resharper用return语句将foreach更改为"IgnoreNullItemsHavingDifferentBehaviour"版本。

public static IEnumerable<T> IgnoreNullItemsHavingSameBehaviour<T>(this IEnumerable<T> source) where T : class
            {
                if (source == null) throw new ArgumentNullException("source");
                foreach (var item in IgnoreNulls(source))
                {
                    yield return item;
                }
                yield break;
            }

为什么使用"yield"延迟执行会有不同的运行时行为?c#中的关键字

异常会立即引发,因为IgnoreNullItemsHavingDifferentBehaviour本身不包含任何"yield"。

相反,它是IgnoreNulls,它被转换为迭代器块,因此使用延迟执行。

这实际上是Jon Skeet在他的EduLinq系列中使用的方法,用于强制对源序列进行立即null检查。查看这篇文章获得更详细的解释(特别是"让我们实现它"部分)。

我还没有测试,但我可以猜…

对于IgnoreNullItems方法,整个方法被延迟到您成为枚举。使用您的替代方法,只有IgnoreNulls的执行被延迟—IgnoreNullItemsHavingDifferentBehaviour中的null检查立即发生。

延迟执行来自yield return的工作方式。它将在一个方法中创建状态机,在您尝试枚举第一项之前,状态机不会启动或执行任何代码。

但是当没有yield return时,它将像普通方法一样运行。

在Jon Skeet的《Edulinq》中有完美的解释和展示。