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