【问题标题】:Interesting use of the C# yield keyword in Nerd Dinner tutorial在 Nerd Dinner 教程中有趣地使用 C# yield 关键字
【发布时间】:2010-12-30 13:46:18
【问题描述】:

通过教程(Professional ASP.NET MVC - Nerd Dinner),我发现了这个 sn-p 代码:

public IEnumerable<RuleViolation> GetRuleViolations() {
    if (String.IsNullOrEmpty(Title))
        yield return new RuleViolation("Title required", "Title");
    if (String.IsNullOrEmpty(Description))
        yield return new RuleViolation("Description required","Description");
    if (String.IsNullOrEmpty(HostedBy))
        yield return new RuleViolation("HostedBy required", "HostedBy");
    if (String.IsNullOrEmpty(Address))
        yield return new RuleViolation("Address required", "Address");
    if (String.IsNullOrEmpty(Country))
        yield return new RuleViolation("Country required", "Country");
    if (String.IsNullOrEmpty(ContactPhone))
        yield return new RuleViolation("Phone# required", "ContactPhone");
    if (!PhoneValidator.IsValidNumber(ContactPhone, Country))
        yield return new RuleViolation("Phone# does not match country", "ContactPhone");
    yield break;
}

我已经阅读了yield,但我想我的理解仍然有点模糊。它似乎要做的是创建一个对象,允许在集合中的项目之间循环而不实际执行循环,除非并且直到绝对必要为止。

不过,这个例子对我来说有点奇怪。我认为它所做的是延迟任何RuleViolation 实例的创建,直到程序员使用for each 或像.ElementAt(2) 这样的LINQ 扩展方法实际请求集合中的特定项目。

不过,除此之外,我还有一些问题:

  1. if 语句的条件部分何时得到评估?何时调用GetRuleViolations() 或实际迭代可枚举?换句话说,如果Title 的值在我调用GetRuleViolations() 和我尝试实际迭代它的时间之间从null 更改为Really Geeky Dinner,是否会创建RuleViolation("Title required", "Title")

  2. 为什么需要yield break;?它到底在做什么?

  3. 假设Title 为空或为空。如果我调用GetRuleViolations(),然后连续两次迭代生成的可枚举,new RuleViolation("Title required", "Title") 将被调用多少次?

【问题讨论】:

  • .Net 编译器将这种语法糖变成了一种更加混蛋的形式。编译示例,然后将 IL 加载到反射器中。您应该能够准确地了解那里发生了什么。

标签: c# ienumerable yield


【解决方案1】:

1) 举个更简单的例子:

public void Enumerate()
{
    foreach (var item in EnumerateItems())
    {
        Console.WriteLine(item);
    }
}

public IEnumerable<string> EnumerateItems()
{
    yield return "item1";
    yield return "item2";
    yield break;
}

每次从IEnumerator 调用MoveNext() 时,代码都会从yield 点返回并移至下一个可执行代码行。

2) yield break; 会告诉 IEnumerator 没有什么要列举的了。

3) 每次枚举一次。

使用yield break;

public IEnumerable<string> EnumerateUntilEmpty()
{
    foreach (var name in nameList)
    {
        if (String.IsNullOrEmpty(name)) yield break;
        yield return name;
    }     
}

【讨论】:

  • 确定“3) 一次”?据我了解,每次都会重新评估(请参阅此处的其他两个答案)。
  • 如果你列了一个列表,为什么不遍历它呢? yield 方式使您无需显式构造列表。以您为例: public IEnumerable Enumerate() { yield return "item1";收益率返回“item2”; }
  • 谢谢,这完成了我最初想要展示的内容,他的示例的简单版本。
  • yield break 在您的第一个示例中也不是必需的。在你的第二个例子中它当然是。
【解决方案2】:

短版:

1:yield 是神奇的“停止并稍后返回”关键字,因此“活动”之前的 if 语句已被评估。

2:yield break 显式结束枚举(在 switch 案例中考虑“break”)

3:每次。当然,您可以缓存结果,例如将其转换为 List 并在之后对其进行迭代。

【讨论】:

    【解决方案3】:

    包含yield 命令的函数的处理方式与普通函数不同。当调用该函数时,幕后发生的事情是匿名类型由函数的特定IEnumerable 类型构造,该函数创建该类型的对象并返回它。匿名类包含在每次调用IEnumerable.MoveNext 时执行函数体直到下一个yield 命令的逻辑。有点误导,函数体不是像普通函数那样批量执行,而是分段执行,每段在枚举器前移一步时执行。

    关于您的问题:

    1. 正如我所说,每个 if 都会在您迭代到下一个元素时执行。
    2. yield break 在上面的例子中确实没有必要。它的作用是终止枚举。
    3. 每次迭代可枚举时,都会再次强制执行代码。在相关行设置断点,自行测试。

    【讨论】:

    • +1 非常清晰和有用的答案,谢谢。如果你不介意的话,只是一个小的后续问题:你能想出一个yield break 必要的例子吗?
    • 小澄清 - 你的意思是 IEnumerator[&lt;T&gt;] 在你说 IEnumerable[&lt;T&gt;] 的几个地方。
    • 所以在示例中yield break 是必要的,因为如果没有创建RuleViolation,它会抛出异常,对吧?
    • “空集”是指没有if 语句评估为真,还是说如果我还没有添加任何if,则需要yield break 作为占位符对我的方法的陈述? (或两者兼而有之?)
    • 好的,但很明显,private IEnumerable&lt;string&gt; Enumerate() { } 会给您一个“并非所有代码路径都返回值错误”。因此,我认为这使我可以得出以下结论:如果您的方法中没有其他返回语句(yield 或其他),您需要 yield break 作为占位符,或者(2)“如果您需要停止函数体内某处的枚举,在执行到达其自然结束之前。”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-29
    • 2014-09-27
    • 2012-07-01
    • 2023-03-05
    • 1970-01-01
    相关资源
    最近更新 更多