【问题标题】:How to resume a method from its last return point?如何从最后一个返回点恢复方法?
【发布时间】:2014-08-30 20:02:51
【问题描述】:

我想知道 C# 中是否有某种机制允许从它在上次调用期间到达的最后一次返回中“恢复”方法。

我需要什么:我有一个抽象语法树 (AST),它是由从源语言设计的解析器创建的。这个抽象语法树是一个“根类”的对象,它具有其他类的字段实例等。我需要做的是创建一个 sequencer,它采用这个抽象语法树,并通过 generator 创建其他语言的指令。指令序列以空值结束。这可以通过生成器方法 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() 并不总是在某种迭代中被调用,所以我不认为 @ 987654326@ 和迭代器是一个很好的解决方案。

这是因为为了使用迭代器,我最终不得不写类似的东西

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

如何做到这一点?

【问题讨论】:

  • 如果您说出您要解决的问题,那将很容易回答。这看起来像XYProblem。顺便说一句,你问的是所谓的状态机。
  • 唯一的问题是你想yield return (nothing); 而不是yield return some_object; 吗?
  • 我同意你的看法,但我不能在这篇文章中发布整个“代码生成器”程序......我应该发布抽象语法树类,也许还有解析器代码.显然,这是不可能的:D
  • 我编辑了整个问题,以便更好地理解问题及其限制。我希望它能帮助你(和我:P)
  • @LovaJ。 - 我想误解来自这样一个事实,即您坚持认为 IEnumerator.Current/IEnumerator.MoveNext() 对绝对不如单个“next()”方法...如果您真的只需要 next() 为什么不包装 C# 创建的迭代器使用单一方法的辅助类?不知道您为什么将 foreachIEnumerable 视为单个不可分离的概念...

标签: android c# iterator yield-return


【解决方案1】:

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

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

【讨论】:

  • 感谢您的回答,但这不是解决我的问题的好方法,因为这意味着我必须探索解析器生成的整个抽象语法树......而我没有想要这样做:我想探索它,直到我到达下一个可能的指令来生成并停在那里。在下一个探索 AST 的方法调用中,我想从上次离开的地方开始。
  • @lovaJ:如果这就是你想做的,那就去做吧。当您生成一个时,yield return 它。如果您不需要返回值,只需返回一个虚拟的int 或其他东西。
  • +1。 @LovaJ。非常不清楚为什么“这意味着我必须探索整个抽象语法树”。没有完成枚举的要求...您可以轻松拥有无限枚举器(而且您绝对不需要枚举所有项目,如int 来返回下一个)。
【解决方案2】:

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

此外,正如Alexei Levenkovhis comment 中指出的那样,您可以使用更准确地满足您需求的舒适方法来包装MoveNextCurrent。您甚至可以为 IEnumerator&lt;Instruction&gt; 类型编写扩展方法:

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
    }
}

【讨论】:

  • 感谢您的回答,这是迄今为止最好的!不过,我还有事要问你:假设在生成器方法next() 中唯一要做的事情是:var enumerator = generator.Generate().GetEnumerator(); return enumerator.Current; 我问你:下次我调用next 时,所以GetEnumerator()Generate() 方法将从我离开的地方开始(所以yield return 行)还是从头开始?
  • @LovaJ。这就是yield 的工作原理:每次调用MoveNext 之后,方法的执行将在yield 语句上“等待”。在您调用 MoveNext 之后,它将从这一点继续 - 到下一个 yield(或者在循环中相同),或者到方法的末尾,在这种情况下,MoveNext 将返回 false .请参阅我在回答中给出的链接中的示例(特别是 yield 链接)。
  • 感谢您的帮助,通过您的回答,我能够实现一个迭代器解决方案来解决我的问题!谢谢你,现在我知道GetEnumerator() 并且我知道迭代器的功能更好,再次感谢! :D
  • @LovaJ。很高兴听你这样说!干杯:)
猜你喜欢
  • 1970-01-01
  • 2015-08-06
  • 2020-03-20
  • 1970-01-01
  • 2021-02-19
  • 1970-01-01
  • 1970-01-01
  • 2018-08-27
相关资源
最近更新 更多