Реализация IEnumerator <T>для фиксированных массивов - PullRequest
2 голосов
/ 12 марта 2020

Мне нужно реализовать изменяемый полигон , который ведет себя как структура, что он копируется по значению и изменения в копии не имеют побочных эффектов для оригинала.

Рассмотрим мою попытку при написании struct для такого типа:

public unsafe struct Polygon : IEnumerable<System.Drawing.PointF>
{
    private int points;
    private fixed float xPoints[64];
    private fixed float yPoints[64];

    public PointF this[int i]
    {
        get => new PointF(xPoints[i], yPoints[i]);
        set
        {
            xPoints[i] = value.X;
            yPoints[i] = value.Y;
        }
    }

    public IEnumerator<PointF> GetEnumerator()
    {
        return new PolygonEnumerator(ref this);
    }
}

У меня есть требование, что Polygon должен быть скопирован по значению, так что это struct.
( Обоснование : Изменение копии не должно иметь побочных эффектов для оригинала. )

Я также хотел бы реализовать IEnumerable<PointF>.
( Обоснование: Возможность писать for (PointF p in poly))

Насколько мне известно, C# не позволяет вам переопределить поведение копирования / назначения для типов значений. Если это возможно , то есть "низко висящий фрукт", который ответил бы на мой вопрос.

Мой подход к реализации поведения копирования по значению Polygon заключается в использовании unsafe и исправлены массивы, позволяющие многоугольнику хранить до 64 точек в самой структуре, что предотвращает косвенное изменение многоугольника через его копии.

Я сталкиваюсь с проблемой, когда я go реализую PolygonEnumerator : IEnumerator<PointF> хотя.
Другое требование ( желаемое за действительное ) состоит в том, что перечислитель вернет PointF значения, которые соответствуют фиксированным массивам Polygon, даже если эти точки были изменены во время итерации.
( Обоснование: перебор массивов работает следующим образом, поэтому этот многоугольник должен вести себя в соответствии с ожиданиями пользователя. )

public class PolygonEnumerator : IEnumerator<PointF>
{
    private int position = -1;

    private ??? poly;

    public PolygonEnumerator(ref Polygon p)
    {
        // I know I need the ref keyword to ensure that the Polygon
        // passed into the constructor is not a copy
        // However, the class can't have a struct reference as a field

        poly = ???;
    }

    public PointF Current => poly[position]; 

    // the rest of the IEnumerator implementation seems straightforward to me

}

Что можно сделать для реализации класс PolygonEnumerator в соответствии с моими требованиями?

Мне кажется, что я не могу сохранить ссылку на исходный многоугольник, поэтому я должен сделать копию его точек. к самому счетчику; Но это означает, что счетчик не сможет увидеть изменения в исходном многоугольнике!

Я полностью согласен с ответом "Это невозможно".

Возможно, я вырыл яму для себя здесь, хотя отсутствует полезная языковая функция или традиционное решение исходной проблемы.

1 Ответ

3 голосов
/ 12 марта 2020

Ваш тип Polygon не должен быть struct, потому что ( 64 + 64 ) * sizeof(float) == 512 bytes. Это означает, что для каждой операции копирования значения потребуется копия в 512 байт, что является очень неэффективным (не в последнюю очередь из-за ссылочного местоположения , которое решительно благоприятствует использованию объектов, которые существуют в одном месте в памяти) .

У меня есть требование, чтобы полигон копировался по значению, чтобы он представлял собой структуру. (Обоснование: изменение копии не должно иметь побочных эффектов на оригинале.)

Ваше «требование» неверно. Вместо этого определите неизменяемый класс с явной операцией копирования - и / или используйте изменяемый объект «строитель» для эффективного построения больших объектов.

Я также хотел бы, чтобы он реализовал IEnumerable<PointF>. (Обоснование: возможность писать для (PointF p в поли))

Это прекрасно - но вам вряд ли когда-нибудь понадобится реализовать IEnumerator<T> непосредственно, потому что C# может сделать это за вас при использовании yield return (и сгенерированный CIL очень оптимизирован!).

Мой подход к реализации поведения Polygon по копированию по значению заключается в использовании небезопасных и фиксированных массивов, чтобы позволить многоугольнику сохранять до 64 точек в самой структуре, что предотвращает косвенное изменение полигона через его копии.

Это не то, как C# должно быть написано. unsafe следует избегать везде, где это возможно (поскольку это нарушает встроенные гарантии и гарантии CLR).

Другое требование (желаемое за действительное) состоит в том, что перечислитель будет возвращать значения PointF, которые соответствуют фиксированным значениям Polygon массивы, даже если эти точки изменены во время итерации. (Обоснование: перебор массивов работает следующим образом, поэтому этот полигон должен вести себя в соответствии с ожиданиями пользователя.)

Кто ваши пользователи / потребители в этом случае? Если вы так обеспокоены тем, чтобы не нарушать ожиданий пользователя, вам не следует использовать unsafe!

. Вместо этого рассмотрите этот подход:

( Обновление: Я только что понял что class Polygon, который я определил ниже, является просто тривиальной оболочкой вокруг ImmutableList<T> - так что вам даже не нужно class Polygon, поэтому просто используйте ImmutableList<Point> вместо)

public struct Point
{
    public Point( Single x, Single y )
    {
        this.X = x;
        this.Y = y;
    }

    public Single X { get; }
    public Single Y { get; }

    // TODO: Implement IEquatable<Point>
}

public class Polygon : IEnumerable<Point>
{
    private readonly ImmutableList<Point> points;

    public Point this[int i] => this.points[i];

    public Int32 Count => this.points[i];

    public Polygon()
    {
        this.points = new ImmutableList<Point>();
    }

    private Polygon( ImmutableList<Point> points )
    {
        this.points = points;
    }

    public IEnumerator<PointF> GetEnumerator()
    {
        //return Enumerable.Range( 0, this.points ).Select( i => this[i] );
        return this.points.GetEnumerator();
    }

    public Polygon AddPoint( Single x, Single y ) => this.AddPoint( new Point( x, y ) );

    public Polygon AddPoint( Point p )
    {
        ImmutableList<Point> nextList = this.points.Add( p );
        return new Polygon( points: nextList );
    }
}
...