2014-08-30 3 views
0

Я хочу знать, есть ли какой-то механизм в C#, который позволяет «возобновить» метод из последнего возвращения, которое оно достигло во время последнего вызова.Как возобновить метод с момента последнего возврата?

ЧТО Я НУЖДАЮ: У меня есть абстрактное синтаксическое дерево (AST), которое создается парсером, разработанным на языке оригинала. Это абстрактное синтаксическое дерево является объектом «корневого класса», который имеет в качестве полей экземпляры других классов и т. Д. Мне нужно создать секвенсор, который принимает это абстрактное дерево синтаксиса и через генератор создает инструкции на другом языке. Последовательность инструкций завершается нулем. Это возможно с помощью метода генератора 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(), но без использования итератора по причинам, которые я объяснял ранее. В зависимости от этого, очень сложно перемещаться внутри АСТ, так как я не могу имеют явную ссылку родителя фактического узла (например, поле, которое является копией фактического узла).

Поскольку этого недостаточно, вы можете рекурсивно позвонить по номеру generate (например, если для перевода есть какой-то итерационный конструктор).

Как достичь этого?

+2

Было бы легко ответить, если вы скажете, что проблема, которую вы пытаетесь решить. Это выглядит как [XYProblem] (http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Кстати, что вы просите, называется state machine. –

+0

Единственная проблема, с которой вы хотите "return return (ничего);" ​​вместо 'yield return some_object;'? –

+0

Я согласен с вами, но я не могу опубликовать всю программу «генератор кода» в этом сообщении ... в зависимости от того, что я должен опубликовать классы абстрактного синтаксического дерева и, возможно, код Parser. Очевидно, это невозможно: D – TwistAndShutter

ответ

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

Спасибо за ваш ответ, это лучший до сих пор! Тем не менее, мне все еще нужно спросить вас: предположим, что единственное, что нужно сделать в методе-генераторе 'next()', это: 'var enumerator = generator.Generate(). GetEnumerator();' 'return enumerator. Current; ' Я спрашиваю вас: в следующий раз, когда я вызову следующий, и поэтому' GetEnumerator() ', метод' Generate() 'начнется с того места, где я ушел (так что' yield return') или с самого начала еще раз? – TwistAndShutter

+0

@LovaJ. Вот как работает «yield»: после каждого вызова «MoveNext» выполнение метода будет «ждать» в этом выражении 'yield'. После вызова 'MoveNext' он будет продолжать с этой точки - либо до следующего' yield' (или того же, если в цикле), либо до конца метода, в случае которого 'MoveNext' вернет' false' ,См. Примеры в ссылках, которые я дал в своем ответе (в частности, ссылка «yield»). – BartoszKP

+1

Спасибо за вашу помощь, благодаря вашим ответам я смог реализовать решение итератора, которое решило мою проблему! Благодаря вам, теперь я знаю 'GetEnumerator()', и я знаю, что функции итераторов лучше, спасибо еще раз! : D – TwistAndShutter

3

Используйте yield return для реализации основной логики. Это создаст перечислитель. Храните его в переменной класса или в другом месте.

Затем используйте этот перечислитель внутри метода-обертки, который возвращает простой объект, вытягивая один новый элемент из перечислителя при каждом вызове.

+0

Спасибо за ваш ответ, но это не очень хорошее решение для моей проблемы, потому что это будет означать, что я должен исследовать всю абстрактную Дерево синтаксиса, генерируемое Parser ... и я не хочу этого делать: я хочу изучить его, пока не дойду до следующей возможной инструкции для создания и остановки там. При следующем вызове метода, который исследует AST, я хочу начать с того места, где я ушел в последний раз. – TwistAndShutter

+0

@lovaJ: Если это то, что вы хотите сделать, тогда сделайте это. Когда вы создаете один, 'yield return' это. Если вам не требуется возвращаемое значение, просто верните фиктивный 'int' или что-то еще. –

+1

+1. @LovaJ. Очень непонятно, почему «это означало бы, что я должен исследовать все абстрактное дерево синтаксиса». Нет никаких требований для завершения перечисления ... Вы можете легко иметь бесконечный счетчик (и вам определенно не нужно перечислять все элементы, такие как 'int', чтобы возвращать следующий). –

Смежные вопросы