如何在实现值对象(作为地址的规范示例)作为不可变对象或结构之间进行选择?
选择一个是否具有表现,语义或任何其他好处?
有几点需要考虑:
在堆栈上(通常)分配结构.它是一种值类型,因此如果数据太大,则跨方法传递数据可能会很昂贵.
在堆上分配一个类.它是一种引用类型,因此通过方法传递对象并不昂贵.
通常,我将结构用于不太大的不可变对象.我只在它们中存有有限数量的数据时才使用它们,或者我想要不变性.一个例子是DateTime
结构.我喜欢认为如果我的对象不像a那样轻量级DateTime
,那么它可能不值得用作结构体.此外,如果我的对象没有意义作为值类型传递(也像DateTime
),那么它可能没有用作结构.不变性在这里是关键.另外,我想强调结构默认情况下不是不可变的.你必须通过设计使它们不变.
在我遇到的99%的情况中,一个类是正确的使用方法.我发现自己不经常需要不可变的课程.在大多数情况下,我认为类是可变的更自然.
我喜欢用思想实验:
当只调用一个空构造函数时,此对象是否有意义?
根据Richard E的要求编辑
一个很好的用法struct
是包装基元并将它们范围限定为有效范围.
例如,概率的有效范围为0-1.使用小数来表示这种情况很容易出错,需要在每个使用点进行验证.
相反,您可以使用验证和其他有用的操作来包装基元.这通过了思想实验,因为大多数原语具有自然的0状态.
以下是struct
表示概率的示例用法:
public struct Probability : IEquatable, IComparable { public static bool operator ==(Probability x, Probability y) { return x.Equals(y); } public static bool operator !=(Probability x, Probability y) { return !(x == y); } public static bool operator >(Probability x, Probability y) { return x.CompareTo(y) > 0; } public static bool operator <(Probability x, Probability y) { return x.CompareTo(y) < 0; } public static Probability operator +(Probability x, Probability y) { return new Probability(x._value + y._value); } public static Probability operator -(Probability x, Probability y) { return new Probability(x._value - y._value); } private decimal _value; public Probability(decimal value) : this() { if(value < 0 || value > 1) { throw new ArgumentOutOfRangeException("value"); } _value = value; } public override bool Equals(object obj) { return obj is Probability && Equals((Probability) obj); } public override int GetHashCode() { return _value.GetHashCode(); } public override string ToString() { return (_value * 100).ToString() + "%"; } public bool Equals(Probability other) { return other._value.Equals(_value); } public int CompareTo(Probability other) { return _value.CompareTo(other._value); } public decimal ToDouble() { return _value; } public decimal WeightOutcome(double outcome) { return _value * outcome; } }
如何在实现值对象(作为地址的规范示例)作为不可变对象或结构之间进行选择?
我认为你的选择是错误的.不可变对象和结构不是对立的,它们也不是唯一的选择.相反,你有四个选择:
类
易变的
一成不变
结构
易变的
一成不变
我认为在.NET中,默认选择应该是表示逻辑的可变类和表示实体的不可变类.实际上,即使对于逻辑实现,我也倾向于选择不可变类,如果可行的话.应该为模拟值语义的小类型保留结构,例如自定义类型,数字类型类似的实体.这里的重点是小的,因为你不想复制大量的数据,而通过引用的间接实际上很便宜(所以我们通过使用结构不会获得太多收益).我总是倾向于制作结构Date
Complex
不可变的(我现在想不出一个例外).由于这最适合内在价值类型的语义,我发现遵循一个很好的规则.
因素:建筑,记忆要求,拳击.
通常,构造函数对结构的限制 - 没有明确的无参数构造函数,没有base
构造 - 决定是否应该使用结构.例如,如果参数的构造函数应该没有初始化的成员为默认值,使用不可变对象.
如果您仍然可以在两者之间做出选择,请确定内存要求.小项应该存储在结构中,特别是如果你期望很多实例.
当实例被装箱时(例如,为匿名功能捕获或存储在非通用容器中),这种好处就会丢失 - 你甚至开始为拳击支付额外费用.
什么是"小",什么是"很多"?
在32位系统上,对象的开销是(IIRC)8字节.请注意,对于几百个实例,这可能已经决定内部循环是否在缓存中完全运行,或者调用GC.如果您期望成千上万的实例,这可能是运行与爬网之间的差异.
从那个POV,使用结构不是一个不成熟的优化.
所以,作为经验法则:
如果大多数实例都会被装箱,请使用不可变对象.
否则,对于小对象,仅当结构构造会导致界面笨拙并且您期望不超过数千个实例时才使用不可变对象.