我意识到结构体是不可变的,变异结构是邪恶的,改变结构中值的正确方法是创建新实例。但是,我不清楚新实例的内存和性能方面/问题与允许结构是可变的。
假设我有结构,
struct Vehicle
{
public readonly int Number;
public readonly double Speed, Direction;
public Vehicle(int number, double speed, double direction)
{
this.Number = number;
this.Speed = speed;
this.Direction = direction;
}
}
并将其创建为:
Vehicle car;
以及
Vehicle plane = new Vehicle(1234, 145.70, 73.20)
如果我需要稍后分配给Number,Speed或Direction,我可以删除readonly并使结构变得可变 - 我知道这样做是最好的。 - 从而“改变”已经创建的结构值。
或者我可以创建一个新的struct实例。所以不要说car.Speed = 120.7;我可以说car = new Vehicle(car.Number,178.55,car.Direction);.这将创建一个新的struct值,几乎就像旧值一样,只有Speed改变了。但它不会改变现有的结构值。
这是问题所在。假设,作为一个极端的例子,我需要每秒更新速度和/或方向数千次。我认为创建这么多实例会严重影响内存和性能,在这种情况下,最好允许结构是可变的。
任何人都可以澄清一个可变结构的内存和性能问题,而不是在这种极端情况下实现结构的正确方法吗?
答案 0 :(得分:3)
你需要自己回答的问题是“我正在编写的类型的预期语义是什么?”
当您尝试撰写value type时,应使用struct
代替class
。值类型的一个很好的例子是DateTime
。无论发生什么,2013年1月12日下午3:33 GMT-7始终是2013年1月12日格林威治标准时间7点3点33分。颜色是另一个好例子;由RGB 255,0,0组成的两种颜色永远不会有任何不同。
您尝试创建的类型Vehicle
没有value semantics,因为您希望它包含要针对特定汽车实例更新的状态。因此,您应该编写一个可变class
而不是struct
。
答案 1 :(得分:2)
结构有两种不同的用例;在某些情况下,需要一个封装单个值的类型,其行为大多类似于一个类,但具有更好的性能和非null的默认值。在这种情况下,人们应该使用我称之为不透明的结构; MSDN的结构指南是在假设这是唯一的用例的情况下编写的。
然而,在其他情况下,结构的目的只是将一些变量与管道胶带绑定在一起。在这些情况下,应该使用透明结构,它只是将这些变量公开为公共字段。这种类型没有任何邪恶。什么是邪恶的概念,一切都应该像一个类对象,或者一切都应该“封装”。如果结构的语义是这样的:
并且对它们的任何更改都会破坏使用它的代码,那么结构的未来版本可能无法执行透明结构无法做到的任何事情,也没有任何透明结构允许的内容该结构的未来版本将能够防止。因此,封装会增加成本,而不会增加价值。
我建议人们应该尽可能地努力使所有结构都透明或不透明。此外,我建议由于.net处理结构方法的方式存在缺陷,应该避免让不透明结构的公共成员修改this
,除非在属性设置器中。具有讽刺意味的是,尽管MSDN的指导原则建议不要将结构用于不代表“单一值”的事物,但在常见情况下,人们只想将一组变量从一段代码传递给另一段代码,透明结构远远优于不透明结构或类类型,并且随着字段数量的增加增长。
CarState
,并让Car
的实例包含CarState
类型的字段可能会有所帮助。这将允许Car
的实例将其状态暴露给外部代码,并可能允许外部代码在受控环境下修改其状态(使用等方法)
delegate void ActionByRef<T1,T2>(ref T1 p1, ref T2 p2);
delegate void ActionByRef<T1,T2,T3>(ref T1 p1, ref T2 p2, T3 p3);
void UpdateState<TP1>(ActionByRef<CarState, TP1> proc, ref TP1 p1)
{ proc(ref myState, ref p1); }
void UpdateState<TP1,TP2>(ActionByRef<CarState, TP1,TP2> proc, ref TP1 p1, ref TP2 p2)
{ proc(ref myState, ref p1, ref p2); }
请注意,此类方法提供了使汽车的状态成为可变类的大部分性能优势,但没有与混杂对象引用相关的危险。 Car
可以让外部代码通过上述方法更新汽车的状态,而不允许外部代码在任何其他时间修改其状态。
Rectangle
的{{1}}和名为R
的{{1}}的结构可被视为包含字段String
,Name
,{ {1}}和X
,它们是相应结构字段的别名。如果可能的话,它将极大地促进类型需要保持比先前预期更多状态的情况。我不认为现在的CIL允许在安全类型中进行这样的别名,但是没有概念上的理由它不能。
答案 2 :(得分:1)
我只会回答这个问题的表现方面。我将为您提供过早的优化警告和有关语义的讨论。
是的,分配整个结构具有性能成本。它引入了变异变体中不存在的任务。我不相信JIT可以优化它们,因为.NET JIT非常简单(它针对快速代码生成进行了优化)。
可能这些分配非常便宜,因为它们在内存中是连续的,并且已经加载了内存缓存行。不需要因为它们而发生内存事务。
性能差异很小(对于合理大小的结构)。不过,你可以测量它。
它还会在您的代码中引入混乱,为每个突变创建一个新的struct实例。我在实践中遇到过一些案例,我发现最好从代码质量角度使用可变结构。这只是少数情况,但它们存在。