Срезы массивов в C#

Как ты делаешь это? Учитывая массив байтов:

byte[] foo = new byte[4096];

Как мне получить первые x байтов массива как отдельный массив? (Конкретно мне он нужен как IEnumerable<byte>)

Это для работы с Socket. Я полагаю, что самый простой способ - это нарезка массива, похожая на синтаксис Perls:

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

Что вернет первые 41 элемент в массив @bar. Есть ли что-то в C#, чего мне просто не хватает, или есть еще что-то, что мне нужно сделать?

LINQ - вариант для меня (.NET 3.5), если это кому-то поможет.

Нарезка массива - это предложение для C# 7.2 github.com/dotnet/csharplang/issues/185

Mark 24.03.2017 11:47

В C# 8.0 будет представлена ​​собственная нарезка массива. См. Ответ для получения более подробной информации

Remy 10.04.2019 09:06

Возможно, вас заинтересует ArraySlice <T>, который реализует нарезку массивов с шагом в качестве представления исходных данных: github.com/henon/SliceAndDice

henon 04.05.2019 21:29
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
259
3
291 995
19
Перейти к ответу Данный вопрос помечен как решенный

Ответы 19

Вы можете использовать метод массивов CopyTo().

Или с LINQ вы можете использовать Skip() и Take() ...

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

+1 для хорошей идеи, но мне нужно использовать возвращенный массив в качестве входных данных для другой функции, что заставляет CopyTo требовать временную переменную. Жду пока других ответов.

Matthew Scharley 02.01.2009 13:53

Я еще не знаком с LINQ, возможно, это еще одно доказательство того, что мне действительно следует знать.

Matthew Scharley 02.01.2009 13:55

этот подход как минимум в 50 раз медленнее, чем Array.Copy. Это не проблема во многих ситуациях, но при циклической нарезке массива падение производительности очень очевидно.

Valentin Vasilyev 04.03.2010 11:48

Я звоню один раз, поэтому производительность для меня не проблема. Это отлично подходит для удобочитаемости ... спасибо.

Rich 24.05.2012 22:46

Спасибо за Skip(). Просто Take() не даст вам произвольного среза. Кроме того, я все равно искал решение LINQ (фрагмент IEnumerable, но я знал, что результаты по массиву будет легче найти).

Tomasz Gandor 29.01.2016 17:53

Я не думаю, что C# поддерживает семантику Range. Однако вы можете написать метод расширения, например:

public static IEnumerator<Byte> Range(this byte[] array, int start, int end);

Но, как говорили другие, если вам не нужно устанавливать начальный индекс, то Take - это все, что вам нужно.

Если вам нужен IEnumerable<byte>, то просто

IEnumerable<byte> data = foo.Take(x);

Вы можете использовать метод расширения Take

var array = new byte[] {1, 2, 3, 4};
var firstTwoItems = array.Take(2);
Ответ принят как подходящий

Массивы можно перечислить, поэтому ваш foo уже сам является IEnumerable<byte>. Просто используйте методы последовательности LINQ, такие как Take(), чтобы получить от него то, что вы хотите (не забудьте включить пространство имен Linq с using System.Linq;):

byte[] foo = new byte[4096];

var bar = foo.Take(41);

Если вам действительно нужен массив из любого значения IEnumerable<byte>, вы можете использовать для этого метод ToArray(). Похоже, здесь дело обстоит не так.

Если мы собираемся копировать в другой массив, просто используйте статический метод Array.Copy. Однако я думаю, что другие ответы правильно интерпретировали намерение, другой массив не требуется, просто IEnumberable <byte>, который занимает первые 41 байт.

AnthonyWJones 02.01.2009 14:09

Обратите внимание, что только одномерные и зубчатые массивы являются перечисляемыми, а многомерные массивы - нет.

Abel 08.04.2010 13:11

Обратите внимание, что использование Array.Copy выполняется намного быстрее, чем использование методов Take или Skip LINQ.

Michael 23.06.2011 08:35

@Abel Это на самом деле очень неверно. Многомерные массивы находятся можно перечислить, но они перечисляются следующим образом: [2,3] => [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]. Неровные массивы также можно перечислить, но вместо того, чтобы возвращать значение при перечислении, они возвращают свой внутренний массив. Как это: type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }

Aidiakapi 09.02.2012 22:49

@Aidiakapi "очень некорректно"? ;). Но вы частично правы, мне следовало написать «многомерные массивы не реализуют IEnumerable<T>», тогда мое утверждение было бы более ясным. См. Также это: stackoverflow.com/questions/721882/…

Abel 10.02.2012 14:00

Вы можете использовать оболочку вокруг исходного массива (который является IList), как в этом (непроверенном) фрагменте кода.

public class SubList<T> : IList<T>
{
    #region Fields

    private readonly int startIndex;
    private readonly int endIndex;
    private readonly int count;
    private readonly IList<T> source;

    #endregion

    public SubList(IList<T> source, int startIndex, int count)
    {
        this.source = source;
        this.startIndex = startIndex;
        this.count = count;
        this.endIndex = this.startIndex + this.count - 1;
    }

    #region IList<T> Members

    public int IndexOf(T item)
    {
        if (item != null)
        {
            for (int i = this.startIndex; i <= this.endIndex; i++)
            {
                if (item.Equals(this.source[i]))
                    return i;
            }
        }
        else
        {
            for (int i = this.startIndex; i <= this.endIndex; i++)
            {
                if (this.source[i] == null)
                    return i;
            }
        }
        return -1;
    }

    public void Insert(int index, T item)
    {
        throw new NotSupportedException();
    }

    public void RemoveAt(int index)
    {
        throw new NotSupportedException();
    }

    public T this[int index]
    {
        get
        {
            if (index >= 0 && index < this.count)
                return this.source[index + this.startIndex];
            else
                throw new IndexOutOfRangeException("index");
        }
        set
        {
            if (index >= 0 && index < this.count)
                this.source[index + this.startIndex] = value;
            else
                throw new IndexOutOfRangeException("index");
        }
    }

    #endregion

    #region ICollection<T> Members

    public void Add(T item)
    {
        throw new NotSupportedException();
    }

    public void Clear()
    {
        throw new NotSupportedException();
    }

    public bool Contains(T item)
    {
        return this.IndexOf(item) >= 0;
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        for (int i=0; i<this.count; i++)
        {
            array[arrayIndex + i] = this.source[i + this.startIndex];
        }
    }

    public int Count
    {
        get { return this.count; }
    }

    public bool IsReadOnly
    {
        get { return true; }
    }

    public bool Remove(T item)
    {
        throw new NotSupportedException();
    }

    #endregion

    #region IEnumerable<T> Members

    public IEnumerator<T> GetEnumerator()
    {
        for (int i = this.startIndex; i < this.endIndex; i++)
        {
            yield return this.source[i];
        }
    }

    #endregion

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion
}

Я бы предложил использовать EqualityComparer.Default для IndexOf - в этом случае вам не понадобится специальный корпус.

Jon Skeet 02.01.2009 14:50

Я ожидал, что все будет в полном порядке. Я бы, конечно, сначала выбрал более простой код.

Jon Skeet 08.04.2010 13:04

Что-то вроде этого, на мой взгляд, лучший выход. Но очевидно, что это больше работы (в первый раз), чем простой Array.Copy, хотя это может иметь много преимуществ, например, SubList буквально является областью в родительском List, а не копией записей в List.

Aidiakapi 09.02.2012 22:55
static byte[] SliceMe(byte[] source, int length)
{
    byte[] destfoo = new byte[length];
    Array.Copy(source, 0, destfoo, 0, length);
    return destfoo;
}

//

var myslice = SliceMe(sourcearray,41);

Я думаю, что Buffer.BlockCopy () более эффективен и дает те же результаты.

Matt Davis 15.02.2012 20:16

Вы можете использовать ArraySegment<T>. Он очень легкий, так как не копирует массив:

string[] a = { "one", "two", "three", "four", "five" };
var segment = new ArraySegment<string>( a, 1, 2 );

К сожалению, это не IEnumerable.

recursive 19.07.2010 21:00

Верно, но было бы легко написать вокруг него оболочку итератора, реализующую IEnumerable.

Mike Scott 18.08.2010 20:58

Кто-нибудь знает, ПОЧЕМУ это не IEnumerable? Я не. Вроде так и должно быть.

Fantius 30.12.2010 01:08

Да, в идеале Array и ArraySegment должны наследоваться от общей базы (IArray?), И оба реализуют ICollection, IEnumerable и IList. Как бы то ни было, ArraySegment не очень полезен.

andrewf 26.01.2012 16:34

@Fantius, потому что ArraySegment<T> является оболочкой для данного массива, не создавая из него копию. Если это IEnumerable<T>, то вы на самом деле не знаете, что за ним стоит, поэтому в итоге вы получите еще одну копию данного IEnumerable<T> в виде массива. Вам нужно будет хранить такой массив, чтобы вы могли предоставить произвольный доступ к любому индексу с O (1) во времени.

Ron Klein 12.02.2012 03:13

@RonKlein, я знаю, что это обертка и не копирует. Остальная часть вашего объяснения потеряла меня.

Fantius 09.03.2012 22:58

ArraySegment - это IList и IEnumerable, начиная с .Net 4.5. Жаль для пользователей более старых версий ..

Todd Li 14.12.2012 01:31

@Yuxiu Li, IEnumerable не новичок в .NET 4.5, он старый как IList из .NET 2.0. msdn.microsoft.com/en-us/library/9eekhta0(v=vs.80).aspx

Zyo 21.03.2013 17:29

@Zyo Я имел в виду, что ArraySegment <T> реализует IEnumerable <T>, начиная с .Net 4.5, а не сам IEnumerable <T> является новым.

Todd Li 23.03.2013 04:36

Полученный ArraySegment будет хорошо повторяться с оператором foreach, но не так сильно с обычным оператором for. Быстрое решение этой проблемы - передать его на IList<T>. В противном случае для нормальной работы потребуется использование параметров int i = theSegment.Offset и i < (theSegment.Offset + theSegment.Count), а также вызов theSegment.Array[i] в теле.

skia.heliou 21.07.2015 20:54

Примечание. ArraySegement выдает исключение, если count больше фактического массива, а Take () - нет.

Jeroen K 20.05.2016 11:16
doesn't copy the array также означает, что он не является потокобезопасным, верно?
Timeless 29.11.2017 09:16

Жаль, что вы не можете создать new ArraySegment<T>(myArraySegment, myOffset, MyCount). Было бы полезно для рекурсии по массивам, например для двоичного поиска по отсортированному массиву.

Jonathan 01.12.2019 17:52

@ Джонатан, разве это не просто new ArraySegment<YourType>(myArraySegment.Array, myOffset, MyCount)? ;)

derHugo 12.03.2021 20:06

Другая возможность, о которой я здесь не упоминал: Buffer.BlockCopy () немного быстрее, чем Array.Copy (), и у него есть дополнительное преимущество, заключающееся в возможности конвертировать на лету из массива примитивов (скажем, коротких []) в массив байтов, что может быть удобно, когда у вас есть числовые массивы, которые нужно передавать через сокеты.

Buffer.BlockCopy дал разные результаты, чем Array.Copy(), хотя они принимают те же параметры - было много пустых элементов. Почему?
jocull 08.08.2012 10:39

@jocull - на самом деле они не принимают одни и те же параметры. Array.Copy () принимает параметры длины и положения в элементах. Buffer.BlockCopy () принимает параметры длины и положения в байтах. Другими словами, если вы хотите скопировать 10-элементный массив целых чисел, вы должны использовать Array.Copy(array1, 0, array2, 0, 10), но Buffer.BlockCopy(array1, 0, array2, 0, 10 * sizeof(int)).

Ken Smith 08.08.2012 19:27
byte[] foo = new byte[4096]; 

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

Вот простой метод расширения, который возвращает срез в виде нового массива:

public static T[] Slice<T>(this T[] arr, uint indexFrom, uint indexTo) {
    if (indexFrom > indexTo) {
        throw new ArgumentOutOfRangeException("indexFrom is bigger than indexTo!");
    }

    uint length = indexTo - indexFrom;
    T[] result = new T[length];
    Array.Copy(arr, indexFrom, result, 0, length);

    return result;
}

Затем вы можете использовать его как:

byte[] slice = foo.Slice(0, 40);

Это может быть решение, которое:

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

Тогда результат является IEnumerable <IEnumerable <байт >>, причем первый IEnumerable <байт> содержит первые 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;
}

Вот функция расширения, которая использует универсальный тип и ведет себя как функция PHP array_slice. Допускаются отрицательные смещение и длина.

public static class Extensions
{
    public static T[] Slice<T>(this T[] arr, int offset, int length)
    {
        int start, end;

        // Determine start index, handling negative offset.
        if (offset < 0)
            start = arr.Length + offset;
        else
            start = offset;

        // Clamp start index to the bounds of the input array.
        if (start < 0)
            start = 0;
        else if (start > arr.Length)
            start = arr.Length;

        // Determine end index, handling negative length.
        if (length < 0)
            end = arr.Length + length;
        else
            end = start + length;

        // Clamp end index to the bounds of the input array.
        if (end < 0)
            end = 0;
        if (end > arr.Length)
            end = arr.Length;

        // Get the array slice.
        int len = end - start;
        T[] result = new T[len];
        for (int i = 0; i < len; i++)
        {
            result[i] = arr[start + i];
        }
        return result;
    }
}

Довольно неплохо, хотя есть кое-что из мира .NET. Если значение start не находится в диапазоне от 0 до arr.Length, вероятно, должно возникнуть исключение за пределами допустимого диапазона. Кроме того, end >= start >= 0, поэтому вам не нужно проверять end < 0, это невозможно. Вероятно, вы могли бы сделать это еще более кратко, проверив то length >= 0, а затем len = Math.min(length, arr.Length - start), вместо того, чтобы возиться с end.

Matthew Scharley 12.11.2014 11:14

Если вы не хотите добавлять LINQ или другие расширения, просто сделайте:

float[] subArray = new List<float>(myArray).GetRange(0, 8).ToArray();
Error CS0246: The type or namespace name 'List<>' could not be found (are you missing a using directive or an assembly reference?) Документация Microsoft безнадежна с сотнями проиндексированных записей «Списка». Что здесь правильное?
wallyk 16.03.2018 01:02
System.Collections.Generic.List
Tetralux 22.11.2018 07:35

Для байтовых массивов System.Buffer.BlockCopy даст вам наилучшую производительность.

Что действительно имеет значение, только если вы делаете это в цикле тысячи или миллионы раз. В приложении для сокетов вы, вероятно, берете какой-то ввод и разбиваете его на части. Если вы делаете это только один раз, наилучшей производительностью будет то, что следующему программисту будет легче всего понять.

Michael Blackburn 23.03.2018 22:40

В C# 7.2 вы можете использовать Span<T>. Преимущество новой системы System.Memory состоит в том, что ей не нужно копировать данные.

Вам нужен метод Slice:

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

Многие методы теперь поддерживают Span и IReadOnlySpan, поэтому использовать этот новый тип будет очень просто.

Обратите внимание, что на момент написания тип Span<T> еще не определен в самой последней версии .NET (4.7.1), поэтому для его использования необходимо установить Пакет System.Memory из NuGet.

Начиная с C# 8.0 / .Net Core 3.0

Будет поддерживаться нарезка массивов, наряду с добавлением новых типов Index и Range.

Документы Range Struct
Документы Index Struct

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


note that at the time of writing this answer, C# 8.0 is not yet officially released
C# 8.x and .Net Core 3.x are now available in Visual Studio 2019 and onwards

любая идея, создает ли это копию массива?

Tim Pohlmann 22.05.2019 15:10

похоже это копия: codejourney.net/2019/02/csharp-8-slicing-indexes-ranges

Tim Pohlmann 23.05.2019 15:51
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace data_seniens
{
    class Program
    {
        static void Main(string[] args)
        {
            //new list
            float [] x=new float[]{11.25f,18.0f,20.0f,10.75f,9.50f, 11.25f, 18.0f, 20.0f, 10.75f, 9.50f };

            //variable
            float eat_sleep_area=x[1]+x[3];
            //print
            foreach (var VARIABLE in x)
            {
                if (VARIABLE < x[7])
                {
                    Console.WriteLine(VARIABLE);
                }
            }



            //keep app run
        Console.ReadLine();
        }
    }
}

C# 8 теперь (с 2019 года) поддерживает Диапазоны, что позволяет значительно упростить создание Slice (аналогично синтаксису JS):

var array = new int[] { 1, 2, 3, 4, 5 };
var slice1 = array[2..^3];    // array[new Range(2, new Index(3, fromEnd: true))]
var slice2 = array[..^3];     // array[Range.EndAt(new Index(3, fromEnd: true))]
var slice3 = array[2..];      // array[Range.StartAt(2)]
var slice4 = array[..];       // array[Range.All]

Вы можете использовать диапазоны вместо хорошо известных функций LINQ: Пропускать(), Брать(), Считать().

Другие вопросы по теме