鉴于可变结构通常被认为是邪恶的(例如,Why are mutable structs “evil”?),是否有潜在的好处可能促使.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
......所以,似乎不变性只是为了避免混淆是一个好主意,但同样,也许在某些情况下值得存在这种混淆。
答案 0 :(得分:13)
这些类型位于System.Windows
命名空间中,通常用于WPF应用程序。应用程序的XAML标记是框架的重要组成部分,因此对于很多事情,它们需要一种使用XAML表达的方式。遗憾的是,没有办法使用WPF XAML调用非参数构造函数(但它可能在松散的XAML中),因此尝试使用适当的参数调用构造函数来初始化它是不可能的。您只能自然地设置对象属性的值,这些属性必须是可变的。
这是件坏事吗?对于这些类型,我会说不。它们仅用于保存数据,仅此而已。如果您希望获得Window
想要的大小,则可以访问DesiredSize
以获取表示所需大小的Size
对象。您不是要通过更改所获得的Width
对象的Height
或Size
属性来“更改所需的大小”,而是通过提供新的{{1}来更改大小对象。我相信这样看是很自然的。
如果这些对象更复杂,操作更复杂或有状态,那么是的,你不希望这些类型既不可变也不结构。然而,由于它们只是尽可能简单和基本(基本上是一个POD),结构在这里是合适的。
答案 1 :(得分:4)
这些类型是可变的,因为与某些人可能声称的相反,可变值类型语义是有用的。有几个地方.net试图假装值类型应该具有与引用类型相同的语义。由于可变值类型语义与可变引用类型语义根本不同,因此假装它们相同会导致问题。然而,这并没有使它们变得“邪恶” - 它只是在对象模型中显示出一个缺陷,它假设对某些东西的复制在语义上等同于对原作的行为。如果有问题的东西是对象引用,则为True;通常是真的 - 但有例外 - 如果它是一个不可变的结构;如果它是一个可变的结构,则为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 [2] .X的值?那么someOtherObject.somePoint.X
呢?
虽然.net集合不太适合存储可变结构,但我仍然会考虑:
Dictionary<string, Point>; ... Point temp = myDict["George"]; temp.X = 9; myDict["George"] = temp;
具有相对清晰的语义,至少在没有线程问题的情况下。虽然我觉得很遗憾.net集合并没有提供一种方法可以简单地说myDict[lookupKey].X = 9;
我仍然认为上面的代码非常明确且不言自明而不必知道任何东西关于Point,除了它有一个名为X的公共整数字段这一事实。相比之下,如果有一个Dictionary<PointClass>
,那么就不清楚应该做些什么来改变与“乔治”。也许与George相关联的PointClass
实例不会在其他任何地方使用,在这种情况下,可以简单地编写适当的字段。另一方面,其他人也可能抓取MyDict["George"]
的副本以捕获其中的值,并且不期望他抓住的PointClass
对象可能会改变。
有些人可能会认为“Point”应该是一个不可变的结构,但只知somePoint.X = 5;
是somePoint
类型的变量,就可以完全确定像Point
这样的语句的效果。 ,反过来又是一个带有名为X
的公共int字段的结构。如果Point
是一个不可变的结构,则必须改为说somePoint = new Point(5, somePoint.Y);
之类的东西,除了速度较慢之外,还需要检查结构以确定它的所有字段都在构造函数中初始化,X是第一个,Y是第二个。在什么意义上,这会比somePoint.X = 5;
改善?
答案 2 :(得分:1)
的可能性:
List<T>.Enumerator
是一个可变结构,因为当时利用经常发生的微操作似乎是个好主意。它几乎是可变结构的“海洋孩子”,因为它被咬了不止一些人。不过,当时对某人来说似乎是个好主意......