为什么System.Windows.Point和System.Windows.Vector是可变的

本文关键字:System Windows Point 为什么 Vector | 更新日期: 2023-09-27 18:20:48

鉴于可变结构通常被认为是邪恶的(例如,为什么可变结构是"邪恶的"?(,是否有潜在的好处可能促使.NET框架的设计者使System.Windows.Point&System.Windows.Vector可变?

我想了解这一点,以便我可以决定使我自己的类似结构可变(如果有的话(是否有意义。使PointVector可变的决定可能只是一个判断错误,但如果有一个很好的理由(例如,性能优势(,我想了解它是什么。

我知道我已经偶然发现了Vector.Normalize()方法的实现几次,因为它,令人惊讶(!(,没有返回新的Vector。它只是改变了电流矢量。

我一直认为它应该像这样工作:

var vector = new Vector(7, 11);
var normalizedVector = vector.Normalize(); // Bzzz! Won't compile

但它实际上是这样工作的:

var vector = new Vector(7, 11);
vector.Normalize(); // This compiles, but now I've overwritten my original vector

。因此,似乎不变性只是为了避免混淆而是一个好主意,但同样,在某些情况下,也许潜在的混淆是值得的。

为什么System.Windows.Point和System.Windows.Vector是可变的

这些类型位于 System.Windows 命名空间中,通常在 WPF 应用程序中使用。 应用程序的 XAML 标记是框架的重要组成部分,因此对于很多事情,它们需要一种使用 XAML 表示的方法。 遗憾的是,无法使用 WPF XAML 调用非无参数构造函数(但在松散 XAML 中是可能的(,因此尝试使用适当的参数调用构造函数来初始化它是不可能的。 您只能设置对象属性的值,因此自然地,这些属性必须是可变的。

这是一件坏事吗? 对于这些类型,我会说不。 它们只是为了保存数据,仅此而已。 如果要获取Window想要的大小,可以访问DesiredSize以获取表示所需大小的Size对象。 您不是要通过更改获得的Size对象的WidthHeight属性来"更改所需的大小",而是通过提供新的Size对象来更改大小。 我相信这样看会更自然。

如果这些对象更复杂,执行更复杂的操作或具有状态,那么是的,您不会希望使这些类型既不可变量也不可变。 但是,由于它们几乎是可以得到的简单和基本(本质上是一个 POD(,因此结构在这里是合适的。

这些类型是可变的,因为与某些人可能声称的相反,可变值类型语义很有用。 在一些地方,.net 尝试假装值类型应具有与引用类型相同的语义。 由于可变值类型语义与可变引用类型语义根本不同,因此假装它们相同会导致问题。 然而,这并不能使它们成为"邪恶的"——它只是显示了对象模型中的一个缺陷,该模型假设对某物的副本进行操作在语义上等同于对原始事物进行操作。 如果所讨论的事物是对象引用,则为 true;通常为真 - 但有例外 - 如果它是一个不可变的结构;如果它是一个可变结构,则为 false。

具有公开字段的结构的一个美妙之处在于,即使是简单的检查,它们的语义也很容易确定。 如果一个人有Point[100] PointArray,他有100个不同的Point实例。 如果有人说PointArray[4].X = 9;,那将改变PointArray的一个项目,而不会改变其他项目。

假设没有使用结构Point,而是有一个可变的类PointClass

class PointClass {public int X; public int Y;};

PointClass[100] PointClassArray中存储了多少个 PointClass 实例? 有什么办法可以说吗? 该语句PointClass[4].X = 9会影响 PointClass[2] 的值吗?X? someOtherObject.somePoint.X呢?

虽然 .net 集合不太适合存储可变结构,但我仍然认为:

字典<字符串,点>;...  点温度 = 我的字典["乔治"];  临时。X = 9;  myDict["George"] = temp;

具有相对清晰的语义,至少在没有线程问题的情况下。 虽然我认为 .net 集合没有提供一种可以简单地说myDict[lookupKey].X = 9;的方法很不幸,但我仍然认为上面的代码非常清晰和不言自明,而不必了解 Point 除了它有一个称为 X 的公共整数字段。 相反,如果一个人有Dictionary<PointClass>,就不清楚应该怎么做才能改变与"乔治"相关的X值。 也许与 George 相关的PointClass实例在其他任何地方都没有使用,在这种情况下,可以简单地编写适当的字段。 另一方面,也有可能是其他人为了捕获其中的值而抓取了MyDict["George"]的副本,并且不希望他抓取的PointClass对象可能会改变。

有些人可能认为"Point"应该是一个不可变的结构,但是像somePoint.X = 5;这样的语句的效果可以完全确定,只知道somePointPoint类型的变量,而而又是一个具有称为X的公共int字段的结构。 如果Point是不可变的结构,则必须说类似 somePoint = new Point(5, somePoint.Y); ,除了速度较慢之外,还需要检查结构以确定其所有字段都在构造函数中初始化,X 是第一个,Y 是第二个。 这在什么意义上会比somePoint.X = 5;有所改进?

顺便说一句,可变结构的最大"陷阱"源于这样一个事实,即系统无法区分改变"this"的结构方法和不改变"this"的结构方法。 一个很大的耻辱。 首选的解决方法是使用返回从旧结构派生的新结构的函数,或者使用接受"ref"结构参数的静态函数。

可能性:

  1. 对于当时不考虑它会咬人的用例的人来说,这似乎是一个好主意。 List<T>.Enumerator是一个可变结构,之所以使用,是因为当时利用经常发生的微选择似乎是一个好主意。它几乎是可变结构是"邪恶"的典型代表,因为它咬了不止几个人。不过,对于当时的人来说,这似乎是一个好主意......
  2. 他们
  3. 确实想到了缺点,但有一些用例,其中性能差异有利于结构(他们并不总是(并且被认为是重要的。
  4. 他们不认为结构是邪恶的。"邪恶"是一种关于不利方面击败正面的观点,而不是一个可证明的事实,即使埃里克·利珀特和乔恩·斯基特说出来,也不是每个人都必须同意某件事。我个人认为他们不是邪恶的,他们只是被误解了;但话又说回来,对于程序员来说,邪恶往往比误解更容易处理,所以这实际上更糟...... ;)也许相关人员不同意。