我正在将以前的class更改为Struct,作为Unity编辑器补丁的一部分。我读过很多关于使用结构的建议:"不允许可变结构",因为糟糕的复制行为会导致修改副本,而且很难跟踪。据我所知,这是基于堆栈且没有数据开销的结果。
但是,我想用具体的案例来澄清这一点。布尔值是否可以在结构体中改变,因为数据大小永远不会改变?特定的布尔属性理论上可以随频率修改,所以如果它可能导致内存问题,我将不得不实现其他一些方法来跟踪其他地方的参数。
额外注释,以防意外的相关性:
- 类有三个属性,其中一个是布尔值。
- 这两个非布尔属性是不可变的。
"可变结构是邪恶的"这句咒语;这本身并没有反映出真正的问题是什么。更准确的版本应该是"在不可变位置的秘密可变结构是邪恶的"。这些位置通常包括非ref
属性或方法返回、readonly
字段或任何非变量表达式。
人们通常感到惊讶的是,他们忘记了结构和类是不同的,并且必须以不同的方式处理。说结构是基于堆栈的是不正确的,相反,它们是"无间接性的"。类的实例有自己的标识——它们通过引用间接传递。结构体的实例是值;它们没有自己的标识符,它们的标识符是存储它们的变量的标识符
与另一个答案相反,这段代码是不是问题:
myObject.MyStructProperty.MyBoolfield = true;
编译器在这种情况下引发CS1612,保护您免受最常见的错误源的影响。它理解这是(在属性突变的情况下,很可能)一个临时值的无用突变,并且自c# 7以来,使用ref
属性无论如何都可以给你想要的。
实际问题是方法。c#在版本8之前没有readonly
方法,这意味着编译器总是必须假设一个方法可能会修改值(必须为readonly
字段创建防御性副本),但是这些方法在所有情况下都必须是可调用的,因为它们也可能只对它们的返回值有用。
myObject.MyStructProperty.FlipBoolfield();
编译器无法警告您这种情况,因为它不知道FlipBoolfield
秘密地改变了的值。如果MyStructProperty
不是ref
,则突变发生在该值的临时副本上,并且更改将丢失。
readonly
,但是如果你愿意的话,保留可变的属性和字段。
因为这是在Unity的环境中,引擎实际上在任何地方都使用了许多可变结构体(和字段),所以你不会冒着遇到错误的风险。
简单地说,这很好:
public struct DayDuration
{
public int Days;
}
这是不行的:
public struct DayDuration
{
public int Days;
// secret struct mutation
public void AddDays(int count)
{
Days += count;
}
}
public struct DayDuration
{
public int Days;
// explicit ref parameter
public static void AddDays(ref DayDuration duration, int count)
{
duration.Days += count;
}
}
或者更好:
public struct DayDuration
{
public int Days;
}
public static class DayDurationExtensions
{
// amazing to use and warns against non-mutable locations
public static void AddDays(this ref DayDuration duration, int count)
{
duration.Days += count;
}
}
你似乎也对"管理费用"感到特别困惑。结构/类。由于结构体缺乏额外的间接性,访问它们的速度更快,因为CPU不需要遍历引用,并且根本不需要调用GC。但是要注意,在没有ref
的情况下,将struct实例(value)分配给不同的位置将复制所有数据,因此您不会从中获得任何内存优化。
数据大小的改变不是结构体不应该改变的原因。
主要原因是可变结构体是按值而不是按引用复制的。典型的例子是使用结构体作为属性:
myObject.MyStructProperty.MyBoolfield = true;
MyStructProperty
将返回一个副本,只有这个副本将被改变,而不是myObject中的实际字段。
如果结构体很小,只需创建一个副本,这是完全安全的,并且创建结构体非常便宜。如果只有三个属性,这种情况很可能发生。你对性能的关注很可能是毫无根据的,在对性能做出假设之前一定要进行测量。
如果struct是very大,您可以允许变异,但您需要非常小心,并在任何地方使用引用以避免复制。