Question

To clarify I'm doing this in Unity3D, which may or may not be important?

I'm trying to figure out if I can pass a value by ref to an IEnumerator function that does not yield. If I try to do it with one that yields, VS2010 complains ("Iterators cannot have ref or out parameters"), but, if I wrap the call up with a similar IEnumerator function that calls the yielding function, but does not yield itself, the error goes away and things appear to work. I'm trying to find out if I'm in unexpected behavior land or if this is normal behavior.

Here's an example of what I'm doing:

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;
}
Was it helpful?

Solution

Looks good. The top function just returns an IEnumerator - but is otherwise a normal function. The bottom function is an IEnumerator [transformed into a funky class by the compiler] and as such cannot have a ref value.

The top function could have been written as such:

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

This is a little more messy - but it shows how this is a normal function that deals with two pieces of data. A int passed by referance, and a IEnumerator [just a class] that it returns [in this example by using out].


Supplemental: This is how stuff works behind the scenes:

    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() { }
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top