Как ты делаешь это? Учитывая массив байтов:
byte[] foo = new byte[4096];
Как мне получить первые x байтов массива как отдельный массив? (Конкретно мне он нужен как IEnumerable<byte>)
Это для работы с Socket. Я полагаю, что самый простой способ - это нарезка массива, похожая на синтаксис Perls:
@bar = @foo[0..40];
Что вернет первые 41 элемент в массив @bar. Есть ли что-то в C#, чего мне просто не хватает, или есть еще что-то, что мне нужно сделать?
LINQ - вариант для меня (.NET 3.5), если это кому-то поможет.
В C# 8.0 будет представлена собственная нарезка массива. См. Ответ для получения более подробной информации
Возможно, вас заинтересует ArraySlice <T>, который реализует нарезку массивов с шагом в качестве представления исходных данных: github.com/henon/SliceAndDice





Вы можете использовать метод массивов CopyTo().
Или с LINQ вы можете использовать Skip() и Take() ...
byte[] arr = {1, 2, 3, 4, 5, 6, 7, 8};
var subset = arr.Skip(2).Take(2);
+1 для хорошей идеи, но мне нужно использовать возвращенный массив в качестве входных данных для другой функции, что заставляет CopyTo требовать временную переменную. Жду пока других ответов.
Я еще не знаком с LINQ, возможно, это еще одно доказательство того, что мне действительно следует знать.
этот подход как минимум в 50 раз медленнее, чем Array.Copy. Это не проблема во многих ситуациях, но при циклической нарезке массива падение производительности очень очевидно.
Я звоню один раз, поэтому производительность для меня не проблема. Это отлично подходит для удобочитаемости ... спасибо.
Спасибо за Skip(). Просто Take() не даст вам произвольного среза. Кроме того, я все равно искал решение LINQ (фрагмент IEnumerable, но я знал, что результаты по массиву будет легче найти).
Я не думаю, что 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 байт.
Обратите внимание, что только одномерные и зубчатые массивы являются перечисляемыми, а многомерные массивы - нет.
Обратите внимание, что использование Array.Copy выполняется намного быстрее, чем использование методов Take или Skip LINQ.
@Abel Это на самом деле очень неверно. Многомерные массивы находятся можно перечислить, но они перечисляются следующим образом: [2,3] => [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]. Неровные массивы также можно перечислить, но вместо того, чтобы возвращать значение при перечислении, они возвращают свой внутренний массив. Как это: type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }
@Aidiakapi "очень некорректно"? ;). Но вы частично правы, мне следовало написать «многомерные массивы не реализуют IEnumerable<T>», тогда мое утверждение было бы более ясным. См. Также это: stackoverflow.com/questions/721882/…
Вы можете использовать оболочку вокруг исходного массива (который является 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 - в этом случае вам не понадобится специальный корпус.
Я ожидал, что все будет в полном порядке. Я бы, конечно, сначала выбрал более простой код.
Что-то вроде этого, на мой взгляд, лучший выход. Но очевидно, что это больше работы (в первый раз), чем простой Array.Copy, хотя это может иметь много преимуществ, например, SubList буквально является областью в родительском List, а не копией записей в List.
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 () более эффективен и дает те же результаты.
Вы можете использовать ArraySegment<T>. Он очень легкий, так как не копирует массив:
string[] a = { "one", "two", "three", "four", "five" };
var segment = new ArraySegment<string>( a, 1, 2 );
К сожалению, это не IEnumerable.
Верно, но было бы легко написать вокруг него оболочку итератора, реализующую IEnumerable.
Кто-нибудь знает, ПОЧЕМУ это не IEnumerable? Я не. Вроде так и должно быть.
Да, в идеале Array и ArraySegment должны наследоваться от общей базы (IArray?), И оба реализуют ICollection, IEnumerable и IList. Как бы то ни было, ArraySegment не очень полезен.
@Fantius, потому что ArraySegment<T> является оболочкой для данного массива, не создавая из него копию. Если это IEnumerable<T>, то вы на самом деле не знаете, что за ним стоит, поэтому в итоге вы получите еще одну копию данного IEnumerable<T> в виде массива. Вам нужно будет хранить такой массив, чтобы вы могли предоставить произвольный доступ к любому индексу с O (1) во времени.
@RonKlein, я знаю, что это обертка и не копирует. Остальная часть вашего объяснения потеряла меня.
ArraySegment - это IList и IEnumerable, начиная с .Net 4.5. Жаль для пользователей более старых версий ..
@Yuxiu Li, IEnumerable не новичок в .NET 4.5, он старый как IList из .NET 2.0. msdn.microsoft.com/en-us/library/9eekhta0(v=vs.80).aspx
@Zyo Я имел в виду, что ArraySegment <T> реализует IEnumerable <T>, начиная с .Net 4.5, а не сам IEnumerable <T> является новым.
Полученный ArraySegment будет хорошо повторяться с оператором foreach, но не так сильно с обычным оператором for. Быстрое решение этой проблемы - передать его на IList<T>. В противном случае для нормальной работы потребуется использование параметров int i = theSegment.Offset и i < (theSegment.Offset + theSegment.Count), а также вызов theSegment.Array[i] в теле.
Примечание. ArraySegement выдает исключение, если count больше фактического массива, а Take () - нет.
doesn't copy the array также означает, что он не является потокобезопасным, верно?
Жаль, что вы не можете создать new ArraySegment<T>(myArraySegment, myOffset, MyCount). Было бы полезно для рекурсии по массивам, например для двоичного поиска по отсортированному массиву.
@ Джонатан, разве это не просто new ArraySegment<YourType>(myArraySegment.Array, myOffset, MyCount)? ;)
Другая возможность, о которой я здесь не упоминал: Buffer.BlockCopy () немного быстрее, чем Array.Copy (), и у него есть дополнительное преимущество, заключающееся в возможности конвертировать на лету из массива примитивов (скажем, коротких []) в массив байтов, что может быть удобно, когда у вас есть числовые массивы, которые нужно передавать через сокеты.
Buffer.BlockCopy дал разные результаты, чем Array.Copy(), хотя они принимают те же параметры - было много пустых элементов. Почему?
@jocull - на самом деле они не принимают одни и те же параметры. Array.Copy () принимает параметры длины и положения в элементах. Buffer.BlockCopy () принимает параметры длины и положения в байтах. Другими словами, если вы хотите скопировать 10-элементный массив целых чисел, вы должны использовать Array.Copy(array1, 0, array2, 0, 10), но Buffer.BlockCopy(array1, 0, array2, 0, 10 * sizeof(int)).
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.
Если вы не хотите добавлять 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 безнадежна с сотнями проиндексированных записей «Списка». Что здесь правильное?
System.Collections.Generic.ListДля байтовых массивов System.Buffer.BlockCopy даст вам наилучшую производительность.
Что действительно имеет значение, только если вы делаете это в цикле тысячи или миллионы раз. В приложении для сокетов вы, вероятно, берете какой-то ввод и разбиваете его на части. Если вы делаете это только один раз, наилучшей производительностью будет то, что следующему программисту будет легче всего понять.
В 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
любая идея, создает ли это копию массива?
похоже это копия: codejourney.net/2019/02/csharp-8-slicing-indexes-ranges
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: Пропускать(), Брать(), Считать().
Нарезка массива - это предложение для C# 7.2 github.com/dotnet/csharplang/issues/185