为什么struct在使用函数时没有改变?

时间:2017-10-13 11:51:51

标签: c# struct value-type

在以下结构中,我使用函数来遵循策略模式。

这是一个简单的范围枚举器。 如果传递负长度,它将反向枚举。

它怎么没有按预期工作。返回_move时,Position保持不变。

我想我知道原因,因为struct正在被复制到某个地方。但我似乎无法找到正在制作副本的地方。

(使用class而不是struct不是我正在寻找的答案。)

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()
    {
    }
}

测试:进行快速测试,请使用以下代码并进行调试。

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

1 个答案:

答案 0 :(得分:0)

问题出在这里。

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

在第一个视图中,您似乎正在使用实际上会对当前值起作用的方法组。但它并没有。编译器 静默 在使用实例方法组时制作struct的副本。

我发现你无法在匿名方法,委托或lambda表达式中使用thisthe reason is explained here.

您必须在本地变量中复制this并在lambda中使用该局部变量。使用实例方法组时会发生同样的事情,但是会默默地发生。如果编译器会在这里发出警告会很好。

无论如何,解决方案是使用静态方法组。如果有更好的解决方案,我想知道。 (使用class而不是struct不是我正在寻找的答案。)

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()
    {
    }
}