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

本文关键字:方法 返回 恢复 最后一个 | 更新日期: 2023-09-27 18:20:02

我想知道C#中是否有某种机制允许从上次调用时到达的最后一个返回"恢复"方法。

我需要什么:我有一个抽象语法树(AST),它是由一个从源语言设计的解析器创建的。这个抽象语法树是一个"根类"的对象,它有其他类的字段实例等等。我需要做的是创建一个序列器,它接受这个抽象语法树并通过生成器创建其他语言的指令。指令序列以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和迭代器不是一个好的解决方案

这是因为为了使用iterotor,我最终不得不写一些类似的东西

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(例如,如果有某种迭代构造函数需要翻译)。

如何做到这一点?

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

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

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

事实上,yieldIEnumerator<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上调用,这要归功于这个答案,它回答了一个类似的问题。

此外,正如阿列克谢·列文科夫在评论中指出的那样,你可以用一种更适合你需求的舒适方法来包装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
    }
}