如何解决避免分配但不允许值类型使用默认零值
C#中的值类型不能具有无参数的ctor,因为创建不带参数的实例时CLR的默认行为是将所有位设为零。
假设我有一个用例,我想拥有一个在基础值上强制某些不变式的类型,而这些不变式则禁止默认的全零状态。这迫使我使用引用类型。现在假设我赚了很多,我的意思是很多这种类型的实例。性能至关重要,这些分配加在一起,给GC带来很大压力。我非常想避免这些分配,因此首先想到使用值类型。但是a,我不能,因为默认值无效。
假定该类型满足值类型所需要的所有其他条件,即它确实表示一个值,具有值语义,最多占用16位。唯一的不足是,全零值是这种类型的无效状态。
我如何实现我的性能目标,而在上述情况下,该目标确实受到了GC压力的限制,而又不会牺牲我的类型所压缩的不变式?
编辑:
一个非常简单的例子,假设我拥有一个64位序列以及第一个非零位的索引。
public <struct/class> BitSequence64
{
private long _bits;
private int _firstNonZero;
public IEnumerable<byte> Bytes => ...
// A bunch of helper properties.
public BitSequence64(long value)
{
// Set the _firstNonZero,etc.
...
}
// Methods that allow you to twiddle the bits but maintaining
// the invariant of always having at least one non-zero.
}
因此,显然将_bits
设置为全零是没有意义的,尤其是因为_firstNonZero
然后将指向第一位,即非零。这些序列很多,我非常希望这种类型的依赖项可以安全地使用它,而不必每次将其传递给面向公众的API时都无需验证其不是default
值。
解决方法
我成功使用的一种技术是更改内部状态的解释方式,以使值类型default(T)
的{{1}}在零无效或无效时有效。希望默认值为零。
究竟要如何实现,将取决于构成类型内部状态的因素。一般而言,成员字段中的至少一个将存储与消费者看到的值不同的值。取而代之的是,该类型的公共接口将说明进入和流出该类型的差异。
了解内部状态类型的自然属性(数学或其他方面)将帮助您确定如何做到这一点。
首先要做的是为该类型选择一个合理的默认值。显然,自然T
被确定为不合理,因此需要选择其他内容。例如,这可能是有效范围内的最小值。但是,无论它是什么,它都会告诉您在存储输入之前如何调整输入,以及在返回它们之前如何调整内部值(通过逆运算)。
入门示例
以下default(T)
包装器类型是该技术的一个非常人为的基本示例。
警告:请勿按原样使用此示例;它仅用于演示目的。
Year
此处,默认值为用于调整内部状态的public readonly struct Year
{
private const int Delta = 2000;
private readonly int _value;
public Year(int value)
{
_value = value - Delta;
}
public int Value => _value + Delta;
}
常量。在Delta
中,default(Year)
将是_value
,但是0
属性将返回Value
。同样,2000
将在输入时将new Year(2000)
转换为2000
,并在输出时转换回0
。另一种思考方式是2000
代表与默认值的偏移量。
围绕此内部表示构建功能时,请务必记住只有构造函数和_value
属性应访问后备字段。其他所有内容,甚至是私有成员,都应使用Value
属性来确保一致性。同样,创建新实例应使用构造函数并传递面向消费者的值。在其他任何地方使用后备字段都会引发潜在的错误,因此最好避免这样做。单元测试对于确保一致性至关重要。
问题类型
Value
的情况有点棘手,因为它具有特定的不变性。在注释中,看起来默认值为BitSequence64
(仅设置了位0)可能是该类型的合理默认值。从现在开始,我将在假设的情况下进行操作。
这可以通过将实际值与1
进行异或来实现。很好,因为XOR'ing by 1是它自己的逆运算。
现在,1
是有效的,因为它表示的值也与default(BitSequence64)
(也有效)所表示的值相同。
new BitSequence64(1L)
使用默认值public struct BitSequence64
{
private const long DefaultBit = 1L;
private long _value;
private int _firstNonZero;
public BitSequence64(long value)
{
if (value == 0)
throw new ArgumentException("At least one bit must be set.",nameof(value));
_value = value ^ DefaultBit;
_firstNonZero = GetFirstNonZero(_value);
}
public long Value => _value ^ DefaultBit;
public int FirstNonZero => _firstNonZero;
// Note that this property uses the post-adjustment,consumer-facing value.
public IEnumerable<byte> Bytes => BitConverter.GetBytes(Value);
private static int GetFirstNonZero(long value)
{
// TODO: Incorporate your implementation here.
throw new NotImplementedException();
}
// And,of course,let's not forget the members that do bit-twiddling
// while maintaining the invariants.
// ...
}
很方便,但是如果您需要使用默认值1
(设置了MSB)怎么办?默认为零时,0x1000_0000_0000_0000
将不再正确。
在内部状态下解决这个问题很容易。我们可以将_firstNonZero
重新定义为距位31“向下”的距离,而不是距位0“向上”的距离。除了按最高有效位对值进行XOR运算外,还修改构造函数和_firstNonZero
属性以对FirstNonZero
执行转换。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。