Riceball's profileRiceball LEE personal we...PhotosBlogLists Tools Help

Blog


    December 29

    C# 2.0中的Yield语句在Delphi中的实现

    Yield与代码协同 (Coroutine)

    C# 2.0 是在不修改 CLR 的前提下由编译器通过有限状态机来实现 iterator block 中 yield 关键字的。
    从而实现将控制和行为分离。
    实际上,这一机制的最终目的是提供一个代码协同(Coroutine)执行的支持机制。参见如下的代码,更能
    说明问题:

    using System.Collections.Generic; public class Tokens : IEnumerable<string> { //iterator block,实现控制枚举的元素 public IEnumerator<string> GetEnumerator() { for(int i = 0; i<elements.Length; i++) yield elements[i]; } ... } foreach (string item in new Tokens()) { //实现终端上打印元素的行为 Console.WriteLine(item); }

    在这段代码执行过程中,foreach 的循环体和 GetEnumerator 函数体实际上是在同一个线程中交替执行的。这是一种介于线程和顺序执行之间的协同执行模式,之所以称之为协同(Coroutine),是因为同时执行的多个代码块之间的调度是由逻辑隐式协同完成的。

    就协同执行而言,从功能上可以分为行为、控制两部分,控制又可进一步细分为控制逻辑和控制状态。行为对应着如何处理目标对象,如上述代码中:行为就是将目标对象打印到终端;控制则是如何遍历这个 elements 数组,可进一步细分为控制逻辑(顺序遍历)和控制状态(当前遍历到哪个元素)。

    其中心思想就是将行为与控制分离,以此来降低代码的耦合度,增强通用性,提高代码的复用率。

    Delphi中Yield的使用

    让我们先看下面一段代码,producer过程(生产者)产生一些数值,而Consumer过程(消费者)则处理值:

    procedure producer(); var i: integer; begin for i := 0 to 100 do if i mod 5 = 0 then consumer(i); end; procedure Consumer(const value: integer); begin writeln(value); end;

    代码似乎很完美,但是在生产者(producer)调用消费过程(consumer)这里出现了耦合,我们希望生产者过程(producer)能增强通用性,降低耦合度,能为不同的消费者服务。
    要达到上述要求,必须要在一个地方维护控制的状态:记住枚举时候的状态。在消费者企图调用生产者产生的值时,生产者不得不在这个地方记住在调用之间的状态。
    在Delphi中我们使用 YieldObj 作为维护控制的状态的地方。请看代码:

    procedure Producer(YieldObj: TMeYieldObject); var i: integer; begin for i := 0 to 100 do if i mod 5 = 0 then YieldObj.Yield(i); end; procedure Consumer(); function GetEnumerator: TYieldInteger; begin Result:= TYieldInteger.Create(Producer); end; begin with GetEnumerator do try While MoveNext do writeln(Current); finally Free; end; end;

    执行Yield方法将退出Producer并返回"i"值作为Yield的参数,不同的是,当Yield返回时候,将会接着上次Yield退出的地方接着执行。
    Yield 的作用就是保持当前状态(局部变量的值),然后返回行为逻辑控制,当下一次执行能接着状态执行。

    消费者函数仅仅是通过MoveNext简单的调用生产者函数抓住每一个Yield的值。只要你想的话,你也可以对所有Producer产生的值进行求和:

    procedure Consumer(); var Sum: integer; begin sum := 0; with TYieldInteger.Create(Producer) do try While MoveNext do begin Inc(Sum, Current); end; finally Free; end; writeln(Sum); end;

    在Delphi2005(D9)以上版本上可以用For..In循环,这样来写(不过为了兼容性,还是使用上述的代码放心):

    type TMyContainer = class public function GetEnumerator: TYieldInteger; end; function TMyContainer.GetEnumerator: TYieldInteger; begin Result:= TYieldInteger.Create(Producer); end; procedure Consumer(); var i: integer; Sum: integer; vMyContainer: TMyContainer; begin sum := 0; vMyContainer := TMyContainer.Create; try for i in vMyContainer do Inc(Sum, i); finally vMyContainer.Free; end; writeln(Sum); end;

    在Delphi上实现 Yield


    本实现是由俄罗斯的牛人 [http://santonov.blogspot.com/ Sergey Antonov] (or Антонов Сергей - aka. 0xffff) 具体实施的。
    本人只是将其稍作修改并移植到了MeObject上。你可以在http://code.google.com/p/meaop/source 中的MeObjects/src/uMeYield.pas中找到。
    不过如果你想使用delphi class类型,你需要打开MeSetting.inc中的 YieldClass_Supports 编译开关。

    在MeObject上的使用

    首先确保关闭MeSetting.inc中的 YieldClass_Supports 编译开关。在写法上略有差别:

    procedure StringYieldProc(YieldObj: PMeYieldObject); var YieldValue: string; i: integer; begin YieldValue:='None'; YieldObj.Yield(YieldValue); for i := 1 to 10 do begin YieldValue := YieldValue + IntToStr(i); YieldObj.Yield(YieldValue); end; end; function GetEnumerator: PYieldString; begin Result:= New(PYieldString, Create(StringYieldProc)); end; //Usage with GetEnumerator^ do try while MoveNext do begin Writeln(Current); end; finally Free; end;

    使用 YieldClass_Supports 编译开关的写法

    {$I MeSetting.inc} //控制逻辑:枚举器 procedure StringYieldProc(YieldObj: {$IFDEF YieldClass_Supports}TMeYieldObject{$ELSE} PMeYieldObject{$endif}); var YieldValue: string; i: integer; begin YieldValue:='None'; YieldObj.Yield(YieldValue);//返回行为逻辑 for i := 1 to 10 do begin YieldValue := YieldValue + IntToStr(i); YieldObj.Yield(YieldValue); //返回行为逻辑 end; end; function GetEnumerator: {$IFDEF YieldClass_Supports}TYieldString {$ELSE}PYieldString{$ENDIF}; begin Result:= {$IFDEF YieldClass_Supports} TYieldString.Create(StringYieldProc){$ELSE}New(PYieldString, Create(StringYieldProc)){$ENDIF}; end; {$IFDEF SUPPORTS_FOR_IN} {$IFDEF YieldClass_Supports} type TMyStrings = class public function GetEnumerator: TYieldString; end; function TMyStrings.GetEnumerator: TYieldString; begin Result:= TYieldString.Create(StringYieldProc); end; var s: string; vStrs: TMyStrings; {$ENDIF} {$ENDIF} begin {$IFDEF SUPPORTS_FOR_IN} {$IFDEF YieldClass_Supports} writeln('Test For_In Yield tEnumerator:'); vStrs := TMyStrings.Create; try for s in vStrs do Writeln(s); finally vStrs.Free; end; {$ENDIF} {$ENDIF} writeln(#13#10'Test Yield tEnumerator:'); with GetEnumerator{$IFNDEF YieldClass_Supports}^{$endif} do try while MoveNext do begin Writeln(Current); end; finally Free; end; {$IFDEF SUPPORTS_FOR_IN} {$IFDEF YieldClass_Supports} s := ''; {$ENDIF} {$ENDIF} end.