【问题标题】:Correct usage of iterator blocks正确使用迭代器块
【发布时间】:2012-03-03 03:44:21
【问题描述】:

我之前正在重构一些代码,但遇到了一个我不太确定的迭代器块的实现。在客户端为某些数据调用外部 API 的系统集成层中,我有一组转换器,它们获取从 API 返回的数据并将其转换为逻辑层中使用的业务实体的集合。一个常见的翻译器类将如下所示:

// translate a collection of entities coming back from an extrernal source into business entities
public static IEnumerable<MyBusinessEnt> Translate(IEnumerable<My3rdPartyEnt> ents) {

    // for each 3rd party ent, create business ent and return collection
    return from ent in ents
           select new MyBusinessEnt {
               Id = ent.Id,
               Code = ent.Code
           };
}

今天我遇到了以下代码。同样,它是一个翻译器类,其目的是将参数中的集合翻译为方法返回类型。但是,这次是一个迭代器块:

// same implementation of a translator but as an iterator block
public static IEnumerable<MyBusinessEnt> Translate(IEnumerable<My3rdPartyEnt> ents) {
    foreach(var ent in ents)
    {
        yield return new MyBusinessEnt {
            Id = ent.Id,
            Code = ent.Code
        };
    }
}

我的问题是:这是对迭代器块的有效使用吗?我看不到以这种方式创建翻译类的好处。这会导致一些意想不到的行为吗?

【问题讨论】:

  • 对我来说似乎完全有效 - 它提供了两个实体之间的编译安全翻译。它有什么问题?

标签: c# .net iterator


【解决方案1】:

您的两个样本做的事情几乎完全相同。查询版本将被重写为对 Select 的调用,并且 Select 的编写方式与您的第二个示例完全相同;它遍历源集合中的每个元素,并返回一个转换后的元素。

这是对迭代器块的完全有效的使用,当然不再需要像这样编写自己的迭代器块,因为您可以使用Select

【讨论】:

    【解决方案2】:

    主要区别在于何时每个代码运行。 第一个被延迟,直到返回值被迭代,而第二个立即运行。我的意思是 for 循环正在强制迭代运行。类公开了IEnumerable&lt;T&gt; 并且在这种情况下被延迟的事实是另一回事。

    这与简单的Select 相比没有任何好处。 yield 的真正威力在于涉及条件:

    foreach(var ent in ents)
    {
        if(someCondition)
        yield return new MyBusinessEnt {
            Id = ent.Id,
            Code = ent.Code
        };
    }
    

    【讨论】:

    • 等一下——您的版本与简单的Where 后跟Select 相比没有任何好处。是的,Chris Shouts 的评论是正确的;两者都推迟执行。
    • @EricLippert 是的,因为他使用的是循环而不是选择。但正如我所说,在这种情况下我只会使用普通的 Linq。
    • 你确定吗?我认为两者都有效地按需创建了它们的值 - 即第二个不会立即运行。
    • @Aliostad:第一个立即返回一个对象,该对象延迟其执行,直到调用 MoveNext。第二个返回一个延迟其执行直到调用 MoveNext 的对象。他们都使用延迟执行。但是,是的,有“两跳”可以从第一个跳出数据,而第二个只有“一跳”。性能差异可能很小。
    • @Aliostad:我认为反对票是“第二个立即运行”。该语句太容易解释为意味着第二个没有延迟执行。
    【解决方案3】:

    是的,效果很好,结果非常相似。

    两者都创建了一个能够返回结果的对象。两者都依赖于可枚举的源保持完整,直到结果完成(或缩短)。两者都使用延迟执行,即在迭代结果时一次创建一个对象。

    不同之处在于第一个返回使用库方法生成枚举器的表达式,而第二个创建自定义枚举器。

    【讨论】:

    • 好的,但是在这个例子中没有必要创建自定义迭代器,对吧?正如@Aliostad 指出的那样,迭代器块最好与条件一起使用。所以也许第二种方法可读性稍差\可调试,因此第一种方法可能更适合这种情况。
    • 另外不同的是,自定义枚举器立即运行,而第一个 (select this from that) 运行延迟。
    • @Aliostad - 那,我不太明白......所以第一个示例返回一个尚未枚举的集合。第二个是在每次调用时枚举返回集合?对吗?
    • @jameslewis:LINQ 可以很好地处理条件。这就是where 的用途。
    • @jameslewis:yield return 基本上会暂停方法中的代码并返回项目,然后当您请求下一个项目时,代码会从该点继续循环,直到到达yield return 再次。因此,代码不会在每次需要从中返回项目时枚举源。
    【解决方案4】:

    第一个例子不是迭代器。它只是创建并返回一个IEnumerable&lt;MyBusinessEnt&gt;

    第二个是迭代器,我看不出有什么问题。每次调用者迭代该方法的返回值时,yield 都会返回一个新元素。

    【讨论】:

      【解决方案5】:

      是的,这是有效的。 foreach 的优点是可调试,所以我更喜欢这种设计。

      【讨论】:

      • 所以迭代器块可能会导致任何试图稍后在系统中通过此业务实体集合调试 foreach 的人感到困惑?
      • 我认为不一定有任何混淆; foreach 块只是使定位正在翻译的行项目变得更容易。如果翻译变得不那么简单,这允许更简单的调试。两者都是完美的实现。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-12
      • 2023-01-13
      • 2015-08-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多