0

In following struct, I'm using function to follow strategy pattern.

this is a simple range enumerator. if negative length is passed, it will enumerate reversely.

How ever it does not work as expected. when _move call is returned, Position remains unchanged.

I guess I know the reason, its because struct is being copied somewhere. but I cant seem to find where copy is being made.

(using class instead of struct is not answer that I'm looking for.)

internal struct RangeEnumerator<T> : IEnumerator<T>
{
    private readonly Func<bool> _move;
    private readonly IReadOnlyList<T> _source;
    private readonly int _start;
    private readonly int _end;

    // position of enumerator. not actual index. negative if reversed
    public int Position { get; private set; }

    public RangeEnumerator(IReadOnlyList<T> source, int start, int length)
    {
        start = Math.Min(Math.Max(start, 0), source.Count);
        _source = source;
        _start = start;
        _end = Math.Min(Math.Max(length + start, 0), source.Count);
        Position = -Math.Sign(length);
        _move = null;
        _move = length >= 0 ? (Func<bool>) this.MoveNextImpl : this.MovePrevImpl;
    }

    public bool MoveNext() => _move();
    public void Reset() => Position = -1;
    public T Current => _source[Position + _start];

    object IEnumerator.Current => Current;

    private bool MoveNextImpl() => ++Position + _start < _end;
    private bool MovePrevImpl() => --Position + _start  >= _end;

    void IDisposable.Dispose()
    {
    }
}

Testing: for quick test, use the following code and debug.

public static class Program
{
    public static void Main(string[] args)
    {
        var list = new List<int> {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        var enumerator = new RangeEnumerator<int>(list, 3, 5); // 3 to 8 exclusive

        foreach (var x in enumerator.AsEnumerable(0))
        {
            Console.WriteLine(x);
        }
    }
}

internal static class EnumeratorExtensions
{
    public static StructEnumerable<TEnumerator, T> AsEnumerable<TEnumerator, T>(this TEnumerator enumerator, T _) where TEnumerator : struct, IEnumerator<T>
    {
        // struct copy doesn't matter since we didn't start enumerating yet.
        return new StructEnumerable<TEnumerator, T>(enumerator); 
    }
}

// Enumerable to be used by foreach.
internal struct StructEnumerable<TEnumerator, T> where TEnumerator : struct, IEnumerator<T>
{
    private TEnumerator _enumerator;

    public StructEnumerable(TEnumerator enumerator)
    {
        // struct copy doesn't matter since we didn't start enumerating yet.
        _enumerator = enumerator;
    }

    public TEnumerator GetEnumerator()
    {
        // struct copy doesn't matter since we didn't start enumerating yet.
        return _enumerator;
    }
}
M.kazem Akhgary
  • 18,645
  • 8
  • 57
  • 118

1 Answers1

0

The issue is here.

_move = length >= 0 ? (Func<bool>) this.MoveNextImpl : this.MovePrevImpl;

In first view, it seems that you are using method group that will actually act on current value. but it doesn't. compiler is silently making a copy of struct when using instance method groups.

I found that you cant use this inside anonymous methods, delegates or lambda expressions. the reason is explained here.

You must copy this in local variable and use that local variable inside lambda instead. the same thing is happening when using instance method groups, but silently. it would be nice if compiler would throw a warning here.

anyway the solution is to use static method groups. If there are better solutions, Id like to know. (using class instead of struct is not answer that I'm looking for.)

internal struct RangeEnumerator<T> : IEnumerator<T>
{
    private readonly MoveStrategy _move;
    private readonly IReadOnlyList<T> _source;
    private readonly int _start;
    private readonly int _end;

    // position of enumerator. not actual index. negative if reversed
    public int Position { get; private set; }

    public RangeEnumerator(IReadOnlyList<T> source, int start, int length)
    {
        start = Math.Min(Math.Max(start, 0), source.Count);
        _source = source;
        _start = start;
        _end = Math.Min(Math.Max(length + start, 0), source.Count);
        Position = -Math.Sign(length); 
        _move = null;

        // no this, therefor no copy
        _move = length >= 0 ? (MoveStrategy)MoveNextImpl : MovePrevImpl;
    }

    public bool MoveNext() => _move(ref this);
    public void Reset() => Position = -1;
    public T Current => _source[Position + _start];

    object IEnumerator.Current => Current;

    private static bool MoveNextImpl(ref RangeEnumerator<T> v) => ++v.Position + v._start < v._end;
    private static bool MovePrevImpl(ref RangeEnumerator<T> v) => --v.Position + v._start  >= v._end;
    private delegate bool MoveStrategy(ref RangeEnumerator<T> v);

    void IDisposable.Dispose()
    {
    }
}
M.kazem Akhgary
  • 18,645
  • 8
  • 57
  • 118