2024-04-14 06:00:02

c#中的数组片

你是怎么做到的?给定一个字节数组:

byte[] foo = new byte[4096];

我如何得到数组的前x字节作为一个单独的数组?(具体来说,我需要它作为一个IEnumerable<byte>)

这是用于使用Sockets的。我认为最简单的方法是数组切片,类似于perl语法:

@bar = @foo[0..40];

将前41个元素返回到@bar数组中。c#中是否有我遗漏的东西,或者有其他我应该做的事情?

LINQ对我来说是一个选择。NET 3.5),如果有帮助的话。


当前回答

从c# 8.0/开始。Net Core 3.0

数组切片将被支持,同时添加了新的类型Index和Range。

范围结构文档 索引结构文档

Index i1 = 3;  // number 3 from beginning
Index i2 = ^4; // number 4 from end
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Console.WriteLine($"{a[i1]}, {a[i2]}"); // "3, 6"

var slice = a[i1..i2]; // { 3, 4, 5 }

以上代码样本摘自c# 8.0博客。

注意,^前缀表示从数组的末尾开始计数。如文档示例所示

var words = new string[]
{
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};              // 9 (or words.Length) ^0

Range和Index也适用于切片数组之外,例如循环

Range range = 1..4; 
foreach (var name in names[range])

将循环1到4项


请注意,在撰写本文时,c# 8.0还没有正式发布 c# 8。. x和。net Core 3。x现已在Visual Studio 2019及以后版本中可用

其他回答

从c# 8.0/开始。Net Core 3.0

数组切片将被支持,同时添加了新的类型Index和Range。

范围结构文档 索引结构文档

Index i1 = 3;  // number 3 from beginning
Index i2 = ^4; // number 4 from end
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Console.WriteLine($"{a[i1]}, {a[i2]}"); // "3, 6"

var slice = a[i1..i2]; // { 3, 4, 5 }

以上代码样本摘自c# 8.0博客。

注意,^前缀表示从数组的末尾开始计数。如文档示例所示

var words = new string[]
{
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};              // 9 (or words.Length) ^0

Range和Index也适用于切片数组之外,例如循环

Range range = 1..4; 
foreach (var name in names[range])

将循环1到4项


请注意,在撰写本文时,c# 8.0还没有正式发布 c# 8。. x和。net Core 3。x现已在Visual Studio 2019及以后版本中可用

byte[] foo = new byte[4096]; 

byte[] bar = foo.Take(40).ToArray();

这可能是一个解决方案:

var result = foo.Slice(40, int.MaxValue);

然后结果是一个IEnumerable< IEnumerable<字节>>,其中第一个IEnumerable<字节>包含foo的前40个字节,第二个IEnumerable<字节>包含其余的字节。

我写了一个包装类,整个迭代是懒惰的,希望对大家有所帮助:

public static class CollectionSlicer
{
    public static IEnumerable<IEnumerable<T>> Slice<T>(this IEnumerable<T> source, params int[] steps)
    {
        if (!steps.Any(step => step != 0))
        {
            throw new InvalidOperationException("Can't slice a collection with step length 0.");
        }
        return new Slicer<T>(source.GetEnumerator(), steps).Slice();
    }
}

public sealed class Slicer<T>
{
    public Slicer(IEnumerator<T> iterator, int[] steps)
    {
        _iterator = iterator;
        _steps = steps;
        _index = 0;
        _currentStep = 0;
        _isHasNext = true;
    }

    public int Index
    {
        get { return _index; }
    }

    public IEnumerable<IEnumerable<T>> Slice()
    {
        var length = _steps.Length;
        var index = 1;
        var step = 0;

        for (var i = 0; _isHasNext; ++i)
        {
            if (i < length)
            {
                step = _steps[i];
                _currentStep = step - 1;
            }

            while (_index < index && _isHasNext)
            {
                _isHasNext = MoveNext();
            }

            if (_isHasNext)
            {
                yield return SliceInternal();
                index += step;
            }
        }
    }

    private IEnumerable<T> SliceInternal()
    {
        if (_currentStep == -1) yield break;
        yield return _iterator.Current;

        for (var count = 0; count < _currentStep && _isHasNext; ++count)
        {
            _isHasNext = MoveNext();

            if (_isHasNext)
            {
                yield return _iterator.Current;
            }
        }
    }

    private bool MoveNext()
    {
        ++_index;
        return _iterator.MoveNext();
    }

    private readonly IEnumerator<T> _iterator;
    private readonly int[] _steps;
    private volatile bool _isHasNext;
    private volatile int _currentStep;
    private volatile int _index;
}

可以使用数组的CopyTo()方法。

或者使用LINQ,你可以使用Skip()和Take()…

byte[] arr = {1, 2, 3, 4, 5, 6, 7, 8};
var subset = arr.Skip(2).Take(2);

在c# 7.2中,您可以使用Span<T>。新制度的好处。内存系统的优点是它不需要到处复制数据。

你需要的方法是切片:

Span<byte> slice = foo.Slice(0, 40);

现在很多方法都支持Span和IReadOnlySpan,所以使用这个新类型非常简单。

请注意,在撰写本文时,Span<T>类型在。net的最新版本(4.7.1)中还没有定义,因此要使用它,您需要安装系统。来自NuGet的内存包。