IEnumerator Cloning

A word of warning.  I’m about to show you something you should probably never do.  I really enjoy using LINQ in C#.  Much of it is founded on IQueryable and IEnumerable types.  One time I was deep diving into the inner workings of enumerable types to try to find a simpler syntax to manage some complex processes.Thanks to some information from Jon Skeet, I know that the C# enumerators are compiled into basic state machines behind the scenes.  I was attempting this to see how well Enumerators could be used for branching in a game’s AI decision tree.  It turns out that it quickly makes it hard to manage .  But, I would still like to show you a little about it.If you were to make an enumerable that never ends, such as calculating prime numbers, it is possible to take a snapshot and continue later.  Every time you hit yield return inside of an IEnumerator<T> value, the variables currently in scope will be saved to properties on a generated class.  Because all the values are stored, they can be copied.  And because it is a generated class, you have to use reflection.Below we have the code for cloning an enumerable along with its current state.

    public static class EnumeratorCloner
    {
        public static T Clone<T>(T source) where T : class, IEnumerator
        {
            var sourceType = source.GetType().UnderlyingSystemType;
            var sourceTypeConstructor = sourceType.GetConstructor(new Type[] { typeof(Int32) });
            var newInstance = sourceTypeConstructor.Invoke(new object[] { -2 }) as T;

            var nonPublicFields = source.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
            var publicFields = source.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
            foreach (var field in nonPublicFields)
            {
                var value = field.GetValue(source);
                field.SetValue(newInstance, value);
            }
            foreach (var field in publicFields)
            {
                var value = field.GetValue(source);
                field.SetValue(newInstance, value);
            }
            return newInstance;
        }
    }

Leave a Reply

Your email address will not be published. Required fields are marked *