我想知道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
(例如,如果有某种迭代构造函数需要翻译)。
如何实现这一目标?
答案 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 Levenkov中his comment指出的那样,您可以使用适合您需求的舒适方法包裹MoveNext
和Current
。您甚至可以为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
}
}