如何从最后一个返回点恢复方法?

时间:2014-08-30 20:02:52

标签: android c# iterator yield-return

我想知道C#中是否存在某种机制,它允许从最后一次调用的最后一次返回中“恢复”一个方法。

我需要什么:我有一个抽象语法树(AST),它是由源语言设计的解析器创建的。此抽象语法树是“根类”的对象,它具有其他类的字段实例,依此类推。我需要做的是创建一个 sequencer ,它接受这个抽象语法树并通过生成器创建其他语言的指令。指令序列以null结尾。这可以通过生成器方法 next() 来实现,该方法调用一个定时器计算方法,该方法可以即时计算下一条指令。换句话说,我无法探索整个抽象语法树,生成所有指令并在每次调用next()时逐个返回它们,但每次我必须创建它们中的每一个发电机呼叫next()。

示例:我将发布一个伪代码,以便您更好地理解该问题。

static void Main(string[] args)
{Parser parser = new Parser (//constructor stuff);
 Sequencer sequencer = new Sequencer(parser.Parse()); //the mehtod Parse() generates the AST
 Generator generator = new Generator(sequencer);
 Instruction instruction = generator.next();
 while(instruction)
      {print instruction
       instruction = generator.next();
      } 
}

重要提示:我希望您理解的最重要的事情是next()并不总是在某种迭代中被调用,所以我不认为foreach和迭代器是一个很好的解决方案。

这是因为为了使用iterotors,我终于写了像

这样的东西
foreach(instruction in next())
       {//do stuff with instruction}

我不想那样做!

但是,我会告诉您应该如何构建next()

Instruction next()
{ return sequencer.generate();}

所以generate()

Instruction generate():
{Insturction instr;
while(I explored the whole AST)
      {if(//actual node in the AST is this kind)
          instr=//generate this instruction
      else if(//actual node in the AST is this other kind)
          instr=//generate this other instruction
      else and so on....
      //if the actual node has some child, then it is new actual node
      **yield return** instruction;
      }
 } 

最复杂的部分是我需要具有yield return行为的东西(所以从我在下一次generate()调用时离开的地方开始,但由于我之前解释过的原因而没有使用迭代器。因为我无法明确引用实际节点的父节点(就像一个实际节点副本的字段),所以在AST内移动是非常困难的。

由于这还不够,你可以递归调用generate(例如,如果有某种迭代构造函数需要翻译)。

如何实现这一目标?

2 个答案:

答案 0 :(得分:3)

使用yield return实现主逻辑。这将创建一个枚举器。将其存储在类成员变量中或其他永久存储变量中。

然后在包装器方法中使用该枚举器,该方法返回普通对象,在每次调用时从枚举器中拉出一个新项。

答案 1 :(得分:1)

事实上,yield以及IEnumerator<T>确实符合您的要求。需要注意的关键点是IEnumerable<T>公开了GetEnumerator方法,这正是您需要的方法。

发电机:

public class Generator
{
    //...

    public IEnumerable<Instruction> Generate()
    {
        // ...
        yield return new Instruction(...);
        // ...
    }
    //...
}

如何以您想要的方式使用它的示例。关键部分是您可以generator.Generate().GetEnumerator();

var enumerator = generator.Generate().GetEnumerator();
while (enumerator.MoveNext())
{
    var instruction = enumerator.Current;
    // do something with instruction

    if (steps > 10)  //some dummy reason to stop ordinary iteration
        break;
}

// Now process generator's results manually

if (!enumerator.MoveNext())
    throw new InstructionMissingException();   // no more instructions left

var followingInstruction = enumerator.Current;

// ...

if (!enumerator.MoveNext())
    throw new InstructionMissingException();   // no more instructions left

var theLastInstruction = enumerator.Current;

// ...

if (enumerator.MoveNext())
    throw new TooMuchInstructionsException(); // unexpected instruction

我注意到GetEnumerator可以在IEnumerable上调用,感谢this answer回答类似的问题。

此外,正如Alexei Levenkovhis comment指出的那样,您可以使用适合您需求的舒适方法包裹MoveNextCurrent。您甚至可以为IEnumerator<Instruction>类型编写扩展方法:

public static class IEnumeratorExtensions
{
    public static Instruction NextInstruction(this IEnumerator<Instruction> @this)
    {
        if (@this.MoveNext())
        {
            return @this.Current;
        }

        return null; // or throw, or whatever you like
    }
}