我在 C#(.NET8 版本)中处理数字时发现了一些奇怪的行为8.0.400
。
我创建了一个自定义类型来包装数字,这是展示行为的最小化代码:
struct Mystery<T>(T value)
where T : IBinaryInteger<T>
{
public T Value { get; } = value;
public int GetByteCount() => Value.GetByteCount();
}
执行代码:
Mystery<BigInteger> mystery = new(short.MaxValue);
// Mystery<BigInteger> mystery = new(new BigInteger(short.MaxValue)); // same behavior
Console.WriteLine(mystery.GetByteCount()); // 4
Console.WriteLine(mystery.Value.GetByteCount()); // 2
Console.WriteLine(((IBinaryInteger<BigInteger>)mystery.Value).GetByteCount()); // 4
输出:
4
2
4
由于我创建了Mystery
用于环绕数字的类型,因此我希望mystery.GetByteCount()
其行为与 完全相同Value.GetByteCount()
。但是,如输出所示,我的代码并未按预期工作。
我哪里做错了?我应该如何修改我的Mystery.GetByteCount()
才能使其返回预期结果Value.GetByteCount()
?
以下是 NUnit 测试,用于阐明预期行为:
[TestCase(0)] // expect 1, but was 4
[TestCase(byte.MaxValue)] // expect 2, but was 4
[TestCase(short.MaxValue)] // expect 2, but was 4
[TestCase(char.MaxValue)] // expect 3, but was 4
[TestCase(int.MaxValue)] // expect 4, PASSED
[TestCase(long.MaxValue)] // expect 8, PASSED
public void MysteryBigInt_GetByteCount(long value)
{
Mystery<BigInteger> mystery = new(value);
BigInteger bigInt = new(value);
int expectedByteCount = bigInt.GetByteCount();
Assert.Multiple(
() =>
{
// PASSED
Assert.That(mystery.Value.GetByteCount(), Is.EqualTo(expectedByteCount));
// ony passed for int and long
Assert.That(mystery.GetByteCount(), Is.EqualTo(expectedByteCount));
});
}
这通常是一种错误的期望。考虑以下情况:
IFoo
类似于IBinaryInteger
,并且Impl
类似于BigInteger
。当您这样做时
mystery.Value.GetByteCount()
,您调用的与用于实现的函数不同 。GetByteCount
BigInteger
IBinaryInteger
事实上,通过显式接口
BigInteger
实现,IBinaryInteger.GetByteCount
因此您将无法直接在 上访问此实现
BigInteger
。您必须通过IBinaryInteger
接口访问它,就像在第三行中通过强制转换为接口一样。直接调用
mystery.Value.GetByteCount()
将会调用另一种方法。这两种方法虽然名称相同,但语义不同。
接口方法执行以下操作:
从实现上看,这个方法似乎总是返回 4 的倍数。大概是因为
BigInteger
内部使用uint
数组来表示整数。这与其他方法的语义一致IBinaryInteger
——((uint)10).GetByteCount()
不会因为值小于 256 就返回 1。另一方面,更古老的,
BigInteger
-specific的GetByteCount
做法是这样的:从的文档中
ToByteArray
,我们可以看到,此方法将返回表示整数所需的最少字节数。到头来,
BigInteger
只是没有IBinaryInteger
按照您想要的方式实现,所以如果您希望所有 3 行都以相同的方式实现,那么您需要在实现中为BigInteger
任何其他IBinaryInteger
您“不喜欢”其实现的类型做出例外。你搞糊涂了——你确实通过了
short
(或short
兼容值)。但是您还指定了泛型类型
BigInteger
,因此您的类型为Mystery<BigInteger>
。因此,调用
Value.GetByteCount()
变成BigInteger.GetByteCount()
- 它将使用 的实现BigInteger
,它始终返回 4,而不是您期望的 2IBinaryInteger<short>
。为了强制正确类型
BinaryInteger
,您需要调整Mystery
结构的实现:并创建如下内容:
那么在所有情况下它都会输出 2。