C# 结构:可变性与性能

PBr*_*nek 5 c# performance struct

我意识到结构是不可变的,改变结构是邪恶的,改变结构中值的正确方法是创建新实例。但是,我不清楚新实例的内存和性能方面/问题与允许结构可变的问题。

假设我有结构,

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

并将其创建为:

Vehicle car;
Run Code Online (Sandbox Code Playgroud)

Vehicle plane = new Vehicle(1234, 145.70, 73.20)
Run Code Online (Sandbox Code Playgroud)

如果我稍后需要分配给数字、速度或方向,我可以删除只读并使结构可变 - 我知道这样做很困难。- 从而“改变”已经创建的结构值。

或者我可以创建一个新的结构实例。所以不要说 car.Speed = 120.7; 我可以说 car = new Vehicle(car.Number, 178.55, car.Direction);。这将创建一个几乎与旧值类似的新结构值,只是速度发生了变化。但它不会改变现有的结构值。

这就是问题所在。假设,作为一个极端的例子,我需要每秒更新速度和/或方向数千次。我认为创建这么多实例会严重影响内存和性能,在这种情况下,最好允许结构是可变的。

任何人都可以澄清可变结构的内存和性能问题以及在这种极端情况下实现结构的正确方法吗?

sup*_*cat 4

结构有两种不同的使用情况;在某些情况下,人们需要一种封装单个值且行为大多类似于类的类型,但具有更好的性能和非空默认值。在这种情况下,应该使用我所说的不透明结构;MSDN 的结构指南是在假设这是唯一的用例的基础上编写的。

然而,在其他情况下,该结构的目的只是用胶带将一些变量绑定在一起。在这些情况下,应该使用一种透明的结构,将这些变量简单地公开为公共字段。这种类型并没有什么邪恶之处。邪恶的是一切都应该像类对象一样运行,或者一切都应该“封装”的观念。如果该结构的语义是这样的:

  1. 有一些固定的可读成员(字段或属性)集,它们公开了其整个状态
  2. 给定这些成员的任何一组所需值,可以使用这些值创建一个实例(不禁止值的组合)。
  3. 结构体的默认值应该是将所有这些成员设置为其各自类型的默认值。

并且对它们的任何更改都会破坏使用它的代码,那么该结构的未来版本就不会做任何透明结构无法做的事情,也不存在透明结构允许未来版本做的事情。结构的版本将能够阻止。因此,封装会增加成本而不会增加价值。

我建议,只要可行,就应该努力使所有结构变得透明或不透明。此外,我建议,由于 .net 处理结构方法的方式存在缺陷,人们应该避免修改不透明结构的公共成员,this除非在属性设置器中。具有讽刺意味的是,虽然 MSDN 的指南建议人们不应该对不代表“单一值”的事物使用结构体,但在通常的情况下,人们只是想将一组变量从一段代码传递到另一段代码,透明结构远远优于不透明结构或类类型,并且优势幅度随着字段数量的增加而增加。

顺便说一句,关于最初的问题,我建议表示您的程序可能想要处理两种事物是有用的:(1)一辆汽车,以及(2)与特定汽车相关的信息。我建议拥有一个 structCarState并拥有Car持有 type 字段的实例可能会有所帮助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); }
Run Code Online (Sandbox Code Playgroud)

请注意,此类方法提供了将汽车状态设为可变类的大部分性能优势,但没有与混杂对象引用相关的危险。可以Car让外部代码可以通过上述方法更新汽车的状态,而不允许外部代码在任何其他时间修改其状态。

顺便说一句,我真的希望 .net 有一种方法来指定“安全”结构或类应被视为封装其一个或多个组成部分的成员[例如,这样持有被调用RectangleRString调用的结构Name可以被视为具有 fields XYWidth、 和 ,Height它们是相应结构体字段的别名。如果这是可能的,那么它将极大地促进类型需要保存比之前预期更多的状态的情况。我认为当前的 CIL 不允许在安全类型中使用这种别名,但没有任何概念上的原因它不能。