首页
学习
活动
专区
圈层
工具
发布

矩形类
EN

Code Review用户
提问于 2017-02-18 20:24:21
回答 3查看 6.2K关注 0票数 4

我正在尝试创建一个私人图形库。即使我这么做是为了好玩,我也想得到一些关于我的编码的反馈。例如,你们对下面的类(矩形)有什么看法?它确实有一些对其他类的依赖,我在这里不包括这些类,只是为了节省空间。如果你们认为他们是相关的,让我知道,我会编辑答案,包括他们。

矩形的依赖关系是:

代码语言:javascript
复制
Point: A 2d point with integer coordinates.
Size: Two integers whos values can only be positive (maybe I should change it to uint?)
RectangleF: A 2d rectangle with float coordinates.

我最重要的问题是:

  1. 如果我不允许负宽度和高度,它们的类型应该是uint吗?我使用int是因为.net中的大多数“大小”变量是int (而不是uint)。Ie: System.Collection.Generic.List‘s计数它是一个int,即使是它也不可能是负的。
  2. 对于我的矩形来说,实现IEnumerable是不是一个糟糕的设计?是违反直觉的吗?

哦,我正在使用公共只读字段(而不是只获取属性),以确保即使是我也不能意外地更改它们的值。

代码语言:javascript
复制
namespace Trauer.Graphics
{
    /// <summary>
    /// Immutable struct to replace mutable struct System.Drawing.Rectangle.
    /// </summary>
    public struct Rectangle : IEquatable<Rectangle>, IEnumerable<Point>
    {
        public readonly int X;
        public readonly int Y;
        public readonly int Width;
        public readonly int Height;

        public int Left => X;
        public int Top => Y;
        public int Right => X + Width;
        public int Bottom => Y + Height;

        public Point Location => new Point(X, Y);
        public Size Size => new Size(Width, Height);

        public static Rectangle Empty;

        public Rectangle(int x, int y, int width, int height)
        {
            if (width < 0)
                throw new ArgumentOutOfRangeException(nameof(width) + " must be equal to or greater than zero.");
            if (height < 0)
                throw new ArgumentOutOfRangeException(nameof(height) + " must be equal to or greater than zero.");

            X = x;
            Y = y;
            Width = width;
            Height = height;
        }

        public Rectangle(Point location, Size size)
        {
            X = location.X;
            Y = location.Y;
            Width = size.Width;
            Height = size.Height;
        }

        public static Rectangle FromLTRB(int left, int top, int right, int bottom) => new Rectangle(left, top, right - left, bottom - top);

        public override string ToString() => $"{{X={X},Y={Y},Width={Width},Height={Height}}}";

        public override int GetHashCode()
        {
            // Thanks Microsoft ._.
            return unchecked((int)((uint)X ^
                        (((uint)Y << 13) | ((uint)Y >> 19)) ^
                        (((uint)Width << 26) | ((uint)Width >> 6)) ^
                        (((uint)Height << 7) | ((uint)Height >> 25))));
        }

        public override bool Equals(object obj) => obj is Rectangle && Equals((Rectangle)obj);

        public bool Equals(Rectangle other)
        {
            return X == other.X &&
                   Y == other.Y &&
                   Width == other.Width &&
                   Height == other.Height;
        }

        public static bool operator ==(Rectangle left, Rectangle right)
        {
            return (left.X == right.X
                    && left.Y == right.Y
                    && left.Width == right.Width
                    && left.Height == right.Height);
        }

        public static bool operator !=(Rectangle left, Rectangle right) => !(left == right);

        public IEnumerator<Point> GetEnumerator()
        {
            return GetContainedPoints().GetGenericEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetContainedPoints().GetEnumerator();
        }

        public Point[] GetContainedPoints()
        {
            var points = new Point[Width * Height];
            int index = 0;
            for (int y = Y; y < Bottom; y++)
            {
                for (int x = X; x < Right; x++)
                {
                    points[index] = new Point(x, y);
                    index += 1;
                }
            }
            return points;
        }

        public System.Drawing.Rectangle ToSystemRectangle() => new System.Drawing.Rectangle(X, Y, Width, Height);

        public RectangleF ToRectangleF() => new RectangleF(X, Y, Width, Height);

        public bool Contains(int x, int y)
        {
            return x >= X &&
                   x < Right &&
                   y >= Y &&
                   y < Bottom;
        }

        public bool Contains(Point pt) => Contains(pt.X, pt.Y);

        public bool Contains(Rectangle other)
        {
            return other.X >= X &&
                   other.Right <= Right &&
                   other.Y >= Y &&
                   other.Bottom <= Bottom;
        }

        public Rectangle Intersect(Rectangle other)
        {
            int biggestX = Math.Max(X, other.X);
            int smallestRight = Math.Min(Right, other.Right);
            int biggestY = Math.Max(Y, other.Y);
            int smallestBottom = Math.Min(Bottom, other.Bottom);

            if (smallestRight >= biggestX && smallestBottom >= biggestY)
                return new Rectangle(biggestX, biggestY, smallestRight - biggestX, smallestBottom - biggestY);
            else
                return Empty;
        }

        public bool IntersectsWith(Rectangle other)
        {
            return other.X < Right &&
                   X < other.Right &&
                   other.Y < Bottom &&
                   Y < other.Bottom;
        }

        public Rectangle Union(Rectangle other)
        {
            int smallestX = Math.Min(X, other.X);
            int biggestRight = Math.Max(Right, other.Right);
            int smallestY = Math.Min(Y, other.Y);
            int biggestBottom = Math.Max(Bottom, other.Bottom);

            return new Rectangle(smallestX, smallestY, biggestRight - smallestX, biggestBottom - smallestY);
        }
    }
}

关于以下几个问题:

问:你为什么要实现自己的图形库?答:当我试图理解为什么系统的位图的Get/Set方法如此缓慢时,我就开始搞砸了。最后,我发现自己玩“我自己的库”非常有趣(即使它不适合于生产,因为它的评论太差,几乎没有单元测试),所以我决定继续玩它。

问:为什么要实现自己的矩形,而不是使用系统的矩形?首先也是最重要的,因为我不喜欢变长方形的想法。我不反对变化无常的东西。但是对于“非常简单的事情”,比如一个表示矩形的结构,我觉得改变它的一个属性确实改变了它的“本质”。所以我应该创造一个新的。第二,因为我试图减少库对System.Graphics的依赖:创建一个依赖于另一个(甚至更大的)的图形库(它不会给表带来任何新的东西)是不对的,对吗?因此,即使这是一个很有趣的项目,我也试图减少我的lib对System.Graphics的依赖。现在,我的Bitmap类很大程度上依赖于系统的读写文件。而且我的大多数类都包含一个"ToSystemXXX“方法。但在未来,如果我实现一种合法的方式来读取/写入位图到一个文件,我就可以删除这些方法,并“免费”的System.Graphics。

问:为什么您的矩形实现IEnumerable?嗯..。这就是我来这里的原因之一:获得反馈。我的推理是,它使其他类变得更精简。例如,位图不需要实现“SetPixels(矩形,颜色)”方法,因为它已经实现了"SetPixels(IEnumerable,Color)“。但是我得到了一种“这是对C#库来说太过丙酮”的感觉。我不确定这样的便利是否能证明它的实现是正确的,因为它伤害了.我不知道。矩形的“语义”。

问:你偷了Mycrosoft的GetHashCode实现吗?是的,._。正如您在我的GetHashCode评论中所看到的。

编辑:我决定我的“形状”不会实现IEnumerable。即使这样,使用IEnumerables也很实用(也很有趣),在这种情况下,我认为这不是一个好的设计选择。主要是因为现在还不清楚在迭代实现IEnumerable的形状时会得到哪些点。在这种情况下,长方形,如果你迭代它,你期望得到它的边缘点还是在它里面的点?还是两者都有?所以是的..。我会重构我的代码。谢谢你的反馈,男孩和女孩!

EN

回答 3

Code Review用户

回答已采纳

发布于 2017-02-20 07:53:47

哦,我正在使用公共只读字段(而不是只获取属性),以确保即使是我也不能意外地更改它们的值。

这是一件好事,但你可能会陷入Empty熊市陷阱。您的Empty字段是public static,因此可以执行以下操作

代码语言:javascript
复制
Rectangle a = new Rectangle(1, 2, 0, 0);
Rectangle.Empty = a;

Console.WriteLine(Rectangle.Empty.Left);  

它产生输出1

改变

公共静态矩形空;

代码语言:javascript
复制
public static readonly Rectangle Empty;  

会移除陷阱。

票数 2
EN

Code Review用户

发布于 2017-02-18 21:53:21

矩形作为IEnumerable

对于我的矩形来说,实现IEnumerable是不是一个糟糕的设计?是违反直觉的吗?

我发现Rectangle实现IEnumerable接口很奇怪,而且违反直觉,除非您有充分的理由来实现它。不幸的是,你没有给出任何如何使用它的例子。即使是FromLTRB也不依赖于这个接口,尽管它可能是一个很好的候选,但是您需要将坐标存储在数组中.或者枚举这些值并检查指标(可能是使用switch)。大量的工作。

除此之外,我不知道该从它得到什么,为什么我会需要它。

无论如何,我认为矩形扩展更适合这个目的,因为这样您就可以编写比具有固定顺序的扩展更多的内容。在扩展名中,您可以按坐标的顺序排列,例如:

代码语言:javascript
复制
public static IEnumerable<Point> AsEnumerableLTRB(this Rectangle rect) { ... }

代码语言:javascript
复制
public static IEnumerable<Point> AsEnumerableTRBL(this Rectangle rect) { ... }

或者,只有一个带有PointOrder选项的扩展。

隐式/显式算子

公共System.Drawing.Rectangle ToSystemRectangle() =>新System.Drawing.Rectangle(X,Y,宽度,高度);公共RectangleF ToRectangleF() => => RectangleF(X,Y,宽度,高度);

作为隐式或显式运算符,这些操作可以做得更好。

我也想知道你为什么需要一个新的长方形?这个能比原来的更好吗?我看到的唯一不同是不可变的,但是除非您的图形库可以直接使用这个矩形,我认为将这种类型转换为本机类型的额外开销是过分的,不值得的。

Int32诉UInt32

如果我不允许负宽度和高度,它们的类型应该是uint吗?我使用int是因为.net中的大多数“大小”变量是int (而不是uint)。Ie: System.Collection.Generic.List‘s计数它是一个int,即使是它也不可能是负的。

我认为关于堆栈溢出的关于使用uint对int的问题可以给您一个很好的答案,为什么您应该使用int而不是uint。在短的中:

UInt32不兼容CLS,这意味着它完全不适合在公共API中使用。如果您要在您的私有API中使用uint,这将意味着要对其他类型进行转换,而且保持相同类型通常更容易,也更安全。

(你也应该阅读其他答案)。

票数 3
EN

Code Review用户

发布于 2017-02-18 20:50:37

矩形结构很好地实现了IMHO。

只想说几句:

  • 不需要再次实现==操作符,只需使用Equals方法即可。
  • 在构造函数中计算一次Right Bottom,以避免多次计算。
  • 我想,RightBottom都在矩形内。
  • XYWidthHeight使用属性而不是公共字段

如果我不允许负宽度和高度,它们的类型应该是uint吗?我使用int是因为.net中的大多数“大小”变量是int (而不是uint)。Ie: System.Collection.Generic.List‘s计数它是一个int,即使是它也不可能是负的。

我不会用uint -在这里抛出一个ArgumentOutOfRangeException是绝对可以的

对于我的矩形来说,实现IEnumerable是不是一个糟糕的设计?是违反直觉的吗?

在我眼里-是的。在对矩形进行迭代时,还不清楚要得到什么。

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/155707

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档