Span<T> 范围运算符没有意义

问题描述

我终生无法理解 Span 的 Range 运算符背后的逻辑。

我有一个包含 IP 协议标头的以下字节数组:

---------------------------------------------------------------------------------
|   |   |   |   |   |   |   |   |   |   | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 9 |
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---------------------------------------------------------------------------------
|                   other data                  |    src ip     |    dest ip    |
---------------------------------------------------------------------------------

使用范围运算符,我需要使用以下值来提取正确的 4 字节数组:

Span<byte> ipHeader = ethernetFrame.IPHeader;
byte[] sourceIpAddress = ipHeader[12..16].ToArray();
byte[] destinationIpAddress = ipHeader[16..20].ToArray();

如果相反,我使用 Slice 方法,我会得到:

Span<byte> ipHeader = ethernetFrame.IPHeader;
byte[] sourceIpAddress = ipHeader.Slice(12,4).ToArray();
byte[] destinationIpAddress = ipHeader.Slice(16,4).ToArray();

要求我给出比实际结束索引值大 1 的结束值的范围运算符背后的逻辑是什么?

性能的角度来看,我也很好奇(因为这个特定的代码块每分钟运行数百万次),无论如何使用 Slice 会更有效吗?

解决方法

我终生无法理解 Span 的 Range 运算符背后的逻辑。

值得注意的是,这并非特定于 Span - C# 中的范围总是预期具有唯一的上限。 (有关更多信息,请参阅 tutorial。)

要求我给出比实际结束索引值大 1 的结束值的范围运算符背后的逻辑是什么?

这只是一个独家上限。上限通常是排他的,这有很多好处。例如,注意您的图片中有两个 | 分隔线吗?它们位于索引 12 和 16 - 这是之前范围的上限和之后范围的下限。相同的数字在两个地方都有用,因此您无需开始加减一。

请注意,排他上限在 for 循环中也很常见。如果您想手动复制该 src IP 地址,我怀疑您会执行以下操作:

for (int index = 12; index < 16; index++)
{
    // Copy item at index
}

我认为这比使用包容性上限更惯用:

for (int index = 12; index <= 15; index++)
{
    // Copy item at index
}

它也适用于指定为开始和长度的范围:

for (int index = start; index < start + length; index++)
{
    // Copy item at index
}

(或者相反,使用唯一的上限,您只需从结尾减去开头即可找到长度,而无需任何“加一”部分。)

从性能的角度来看,我也很好奇(因为这个特定的代码块每分钟运行数百万次),无论如何使用 Slice 会更有效吗?

范围运算符已经将跨度切片。可能效率低下的是构造新的字节数组 - 如果您可以避免这种情况,并直接使用跨度,那么效率会更高。

,

可以在 here 中找到指向设计决策(包含与独占)的有趣链接。

报告中的结论是:

  • 它允许 a.Length 作为端点,而无需加/减 1。
  • 它让一个范围的结束成为下一个不重叠的开始
  • 它避免了 x..x-1 形式的丑陋空范围