为什么System.Windows.Point和System.Windows.Vector是可变的?
鉴于可变结构通常被认为是邪恶的(例如, 为什么可变结构“邪恶”? ),是否有潜在的好处可能促使.NET框架的设计者制作System.Windows.Point
和System.Windows.Vector
可变的?
我想理解这一点,所以我可以决定是否有必要使我自己的类似结构可变(如果有的话)。 将Point
和Vector
变为可变的决定可能只是判断错误,但如果有充分的理由(例如,性能优势),我想了解它是什么。
我知道我偶然发现了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
命名空间中,通常用于WPF应用程序。 应用程序的XAML标记是框架的重要组成部分,因此对于很多事情,它们需要一种使用XAML表达的方式。 不幸的是,没有办法使用WPF XAML调用非参数构造函数(但是可以在松散的XAML中调用),因此尝试使用适当的参数调用构造函数来初始化它是不可能的。 您只能自然地设置对象属性的值,这些属性需要是可变的。
这是坏事吗? 对于这些类型,我会说不。 它们仅用于保存数据,仅此而已。 如果你想获得Window
想要的大小,你可以访问DesiredSize
来获得Size
对象来表示它想要的大小。 您不打算通过更改所获得的Size
对象的Width
或Height
属性来“更改所需大小”,您可以通过提供新的Size
对象来更改大小。 我相信这样看是很自然的。
如果这些对象更复杂,操作更复杂或有状态,那么是的,你不希望这些类型既不可变也不结构。 然而,由于它们只是尽可能简单和基本(基本上是POD),结构在这里是合适的。
这些类型是可变的,因为与某些人可能声称的相反, 可变值值类型语义是有用的 。 有几个地方.net试图假装值类型应该具有与引用类型相同的语义。 由于可变值类型语义与可变引用类型语义根本不同,因此假装它们相同会导致问题。 然而,这并不会使它们变得“邪恶” – 它只是在对象模型中显示出一个缺陷,它假定对某个东西的副本进行操作将在语义上等同于对原始动作进行操作。 如果有问题的东西是对象引用,则为真; 通常是真的 – 但有例外 – 如果它是一个不可变的结构; 如果它是一个可变的结构,则为false。
关于具有暴露场的结构的美妙事物之一是它们的语义很容易通过简单的检查来确定。 如果有一个Point[100] PointArray
,则一个有100个不同的Point
实例。 如果有人说PointArray[4].X = 9;
,这将改变PointArray
一个项目,而不是其他项目。
假设不使用struct Point
,而是使用了一个可变类PointClass
:
class PointClass {public int X; public int Y;};
PointClass[100] PointClassArray
中存储了多少个PointClass实例? 有什么方法可以说出来吗? PointClass[4].X = 9
语句PointClass[4].X = 9
会影响PointClass [2] .X的值? someOtherObject.somePoint.X
怎么样?
虽然.net集合不太适合存储可变结构,但我仍然会考虑:
字典; ... Point temp = myDict [“George”]; temp.X = 9; myDict [“George”] = temp;
具有相对清晰的语义,至少在没有线程问题的情况下。 虽然我觉得很遗憾.net集合并没有提供一种方法可以简单地说myDict[lookupKey].X = 9;
我仍然认为上面的代码非常明确且不言自明,除了它有一个名为X的公共整数字段这一事实之外,不必了解Point。相反,如果有一个Dictionary
,它将是不清楚应该做些什么来改变与“乔治”相关的X值。 也许与George相关的PointClass
实例不会在其他任何地方使用,在这种情况下,可以简单地编写适当的字段。 另一方面,其他人也可能抓住了MyDict["George"]
的副本以捕获其中的值,并且不期望他抓住的PointClass
对象可能会改变。
有些人可能认为“Point”应该是一个不可变的结构,但是像somePoint.X = 5;
这样的语句的效果somePoint.X = 5;
可以完全确定,只知道somePoint
是Point
类型的变量,而Point
又是一个带有名为X
的公共int字段的结构。 如果Point
是一个不可变的结构,那么就必须改为像somePoint = new Point(5, somePoint.Y);
除了速度较慢之外,还需要检查结构以确定其构造函数中的所有字段都已初始化,其中X为第一个,Y为第二个。 在某种意义上,这somePoint.X = 5;
有所改进somePoint.X = 5;
?
BTW,具有可变结构的最大“陷阱”源于这样一个事实:系统无法区分改变’this’的结构方法和不改变’this’的结构方法。 一个重大的耻辱。 首选的解决方法是使用返回从旧结构派生的新结构的函数,或者使用接受“ref”结构参数的静态函数。
可能性:
- 当时对于那些不考虑会咬人的用例的人来说,这似乎是一个好主意。
List
是一个可变的结构,因为当时利用经常发生的微操作似乎是个好主意。 它几乎是可变结构“邪恶”的典型代表,因为它被咬了几个人。 不过,当时对某人来说似乎是个好主意…….Enumerator - 他们确实考虑了缺点,但他们知道一些用例,其中性能差异在结构有利(它们并不总是)并且被认为是重要的。
- 他们不认为结构是邪恶的。 “邪恶”是一种关于下边打击上升的观点,而不是一个可certificate的事实,即使Eric Lippert和Jon Skeet说出来,也不是每个人都同意某些事情。 我个人认为他们不是邪恶的,他们只是被误解了; 但话说回来,邪恶通常比对程序员的误解更容易处理,所以实际上更糟糕……;)也许那些参与者不同意。