Передача значения по ссылке в функцию IENumerator?

Чтобы уточнить, что я делаю это в Unity3D, что может быть важно, а может и нет?

Я пытаюсь выяснить, могу ли я передать значение по ссылке функции IEnumerator, которая не дает результата. Если я попытаюсь сделать это с помощью функции yield, VS2010 жалуется («Итераторы не могут иметь параметры ref или out»), но если я оберну вызов аналогичной функцией IEnumerator, которая вызывает функцию yield, но не возвращает себя, ошибка исчезает, и все похоже работает. Я пытаюсь выяснить, нахожусь ли я в стране неожиданного поведения или это нормальное поведение.

Вот пример того, что я делаю:

IEnumerator Wrapper(ref int value)
{
    int tmp = ++value;    // This is the ONLY place I want the value
    return Foo(tmp);      // of the ref parameter to change!  
}                         // I do _NOT_ want the value of the ref
                          // parameter to change in Foo()!
IENumerator Foo(int value)
{
    // blah blah
    someFunc(value);
    someSlowFunc();
    yield return null;
    yield return null;
}

person Purebe    schedule 01.08.2012    source источник
comment
Ну, не ожидайте, что value изменится после первоначального приращения.   -  person zneak    schedule 01.08.2012
comment
Я хочу, чтобы он менялся только тогда, когда я увеличиваю его в Wrapper. В моем коде есть значение, которое нужно увеличить один и только один раз перед вызовом кода, поэтому я хотел обернуть его, чтобы случайно не забыть сделать это в какой-то момент и не ввести трудно отслеживаемые ошибки. (Обновлен вопрос, чтобы быть немного яснее в этом отношении, спасибо!)   -  person Purebe    schedule 01.08.2012
comment
Обратите внимание, что теперь вы можете очень легко... answers.unity3d.com/answers/551381/ view.html   -  person Fattie    schedule 29.01.2016


Ответы (1)


Выглядит неплохо. Верхняя функция просто возвращает IEnumerator, но в остальном это обычная функция. Нижняя функция является IEnumerator [преобразованным компилятором в классный класс] и поэтому не может иметь значения ref.

Функция top могла бы быть записана так:

 void Wrapper(ref int value, out IEnumerator coroutine)
 {
     int tmp = ++value;
     coroutine = Foo(tmp);
 }

Это немного более запутанно, но оно показывает, что это нормальная функция, работающая с двумя фрагментами данных. Int, переданный по ссылке, и IEnumerator [просто класс], который он возвращает [в этом примере с помощью out].


Дополнительно: Вот как все работает за кулисами:

    static void Main(string[] args)
    {
        //Lets get the 'IEnumerable Class' that RandomNum gets compiled down into.
        var IEnumeratorClass = RandomNum(10, 10);

        //All an IEnumerable is is a class with 'GetEnumerator'... so lets get it!
        var IEnumerableClass = IEnumeratorClass.GetEnumerator();

        //It can be used like so:
        while (IEnumerableClass.MoveNext())
        {
            Console.WriteLine(IEnumerableClass.Current);
        }

        Console.WriteLine(new String('-', 10));

        //Of course, that's a lot of code for a simple job.
        //Luckily - there's some nice built in functionality to make use of this.
        //This is the same as above, but much shorter
        foreach (var random in RandomNum(10, 10)) Console.WriteLine(random);

        Console.WriteLine(new String('-', 10));

        //These simple concepts are behind Unity3D coroutines, and Linq [which uses chaining extensively]
        Enumerable.Range(0, 100).Where(x => x % 2 == 0).Take(5).ToList().ForEach(Console.WriteLine);

        Console.ReadLine();
    }

    static Random rnd = new Random();
    static IEnumerable<int> RandomNum(int max, int count)
    {
        for (int i = 0; i < count; i++) yield return rnd.Next(i);
    }

    //This is an example of what the compiler generates for RandomNum, see how boring it is?
    public class RandomNumIEnumerableCompiled : IEnumerable<int>
    {
        int max, count;
        Random _rnd;
        public RandomNumIEnumerableCompiled(int max, int count)
        {
            this.max = max;
            this.count = count;
            _rnd = rnd;
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return new RandomNumIEnumeratorCompiled(max, count, rnd);
        }

        IEnumerator<int> IEnumerable<int>.GetEnumerator()
        {
            return new RandomNumIEnumeratorCompiled(max, count, rnd);
        }

    }
    public class RandomNumIEnumeratorCompiled : IEnumerator<int>
    {
        int max, count;
        Random _rnd;
        int current;
        int currentCount = 0;
        public RandomNumIEnumeratorCompiled(int max, int count, Random rnd)
        {
            this.max = max;
            this.count = count;
            _rnd = rnd;
        }

        int IEnumerator<int>.Current { get { return current; } }

        object IEnumerator.Current { get { return current; } }

        public bool MoveNext()
        {
            if (currentCount < count)
            {
                currentCount++;
                current = rnd.Next(max);
                return true;
            }
            return false;
        }

        public void Reset() { currentCount = 0; }
        public void Dispose() { }
    }
person NPSF3000    schedule 01.08.2012
comment
В моем реальном коде я выполняю загрузку на уровне сети, и я могу предоставить значение префикса для всех объектов networkview, которое должно увеличиваться каждый раз, когда загружается новый уровень. Это делается для того, чтобы старые вызовы с предыдущего уровня не влияли на новый загруженный уровень. Это должно происходить каждый раз при вызове этой функции Foo(), и если я этого не сделаю, это приведет к трудно отслеживаемым ошибкам. Вот почему я хотел убедиться, что это всегда происходит, вместо того, чтобы полагаться на то, что я не забываю предварительно формировать приращение вручную, когда я вызываю функцию. - person Purebe; 01.08.2012
comment
Также спасибо за ответ. Теперь это имеет смысл (я больше читал о том, что такое функции IEnumerator, но я не был уверен, как это работает, пока не прочитал ваш ответ, так что спасибо!) - person Purebe; 01.08.2012
comment
Хе-хе-хе, звучит хорошо :) Только что сделал небольшое редактирование, чтобы прояснить, что происходит. IEnumerator [и IEnumerable] немного сбивает с толку из-за фоновой магии, но очень полезен, если вы знаете, как они работают :) - person NPSF3000; 01.08.2012
comment
И последнее: это не имеет значения, но, возможно, кто-то найдет это полезным, в unity3d вы не можете запускать функции сопрограммы, вызывая их в определенных встроенных функциях (таких как Start() и FixedUpdate()) - поэтому вы должны использовать их функцию StartCoroutine(), который требует, чтобы функция-оболочка возвращала IEnumerator. Это, наверное, и вызвало у меня большое замешательство! - person Purebe; 01.08.2012
comment
Я разработчик Unity, так что я понимаю. То, что вы видите, не имеет отношения к единству — IEnumerator просто возвращает IEnumerator. Точно так же функция типа List‹› просто возвращает List‹› — хотя в первом случае компилятор «автоматически» создаст для вас класс IEnumerator. StartCoroutine — это функция, которая использует этот IEnumerator и делает что-то. - person NPSF3000; 01.08.2012