В чем польза от класса ArraySegment<T>?
Я только что натолкнулся на ArraySegment<byte>
тип при создании подкласса MessageEncoder
учебный класс.
Теперь я понимаю, что это сегмент заданного массива, он принимает смещение, не перечисляется и не имеет индексатора, но я все еще не понимаю его использования. Может кто-нибудь объяснить, пожалуйста, с примером?
7 ответов
ArraySegment<T>
стал намного более полезным в.NET 4.5, поскольку теперь он реализует:
IList<T>
ICollection<T>
IEnumerable<T>
IEnumerable
IReadOnlyList<T>
IReadOnlyCollection<T>
в отличие от версии .NET 4, в которой не реализовано никаких интерфейсов.
Теперь класс может участвовать в чудесном мире LINQ, поэтому мы можем выполнять обычные вещи LINQ, такие как запрос содержимого, обращение содержимого без воздействия на исходный массив, получение первого элемента и т. Д.
var array = new byte[] { 5, 8, 9, 20, 70, 44, 2, 4 };
array.Dump();
var segment = new ArraySegment<byte>(array, 2, 3);
segment.Dump(); // output: 9, 20, 70
segment.Reverse().Dump(); // output 70, 20, 9
segment.Any(s => s == 99).Dump(); // output false
segment.First().Dump(); // output 9
array.Dump(); // no change
Это маленькая структура солдата, которая делает только ссылку на массив и хранит диапазон индекса. Немного опасно, остерегайтесь того, что он не делает копию данных массива и никоим образом не делает массив неизменным или не выражает необходимость неизменности. Более типичный шаблон программирования - просто сохранить или передать массив и переменную или параметр длины, как это делается в методах.NET BeginRead(), String.SubString(), Encoding.GetString() и т. Д. И т. Д.
Он не находит большого применения в.NET Framework, за исключением того, что похоже на одного конкретного программиста Microsoft, который работал над веб-сокетами и любил его в WCF. Это, вероятно, правильное руководство, если вам это нравится, то используйте его. В.NET 4.6 это было сделано в прятки, его использует добавленный метод MemoryStream.TryGetBuffer(). Предпочтительнее, чем два out
аргументы я предполагаю.
В целом, более универсальное понятие срезов высоко в списке пожеланий основных инженеров.NET, таких как Мадс Торгерсен и Стивен Туб. Последний стартовал array[:]
Предложение синтаксиса некоторое время назад, вы можете увидеть, о чем они думают, на этой странице Roslyn. Я предполагаю, что получение поддержки CLR - это то, от чего в конечном итоге зависит. Об этом активно думают в C# версии 7, следите за System.Slices.
Обновление: мертвая ссылка, эта версия поставляется в версии 7.2 как Span.
Update2: больше поддержки в C# версии 8.0 с типами Range и Index и методом Slice().
- Распределение буфера для классов ввода-вывода - используйте один и тот же буфер для одновременных операций чтения и записи и имейте единую структуру, которую вы можете передать, описывая всю вашу операцию.
- Функции набора - говоря математически, вы можете представить любые смежные подмножества, используя эту новую структуру. Это в основном означает, что вы можете создавать разделы массива, но вы не можете представить, скажем, все шансы и все четные. Обратите внимание, что телефонный тизер, предложенный The1, мог быть элегантно решен с использованием разбиения ArraySegment и древовидной структуры. Окончательные числа можно было бы выписать, пройдя сначала глубину дерева. Я считаю, что это был бы идеальный сценарий с точки зрения памяти и скорости.
- Многопоточность - теперь вы можете создавать несколько потоков для работы с одним и тем же источником данных, используя сегментированные массивы в качестве шлюза управления. Циклы, в которых используются дискретные вычисления, теперь могут быть обработаны довольно легко, что последние компиляторы C++ начинают делать в качестве шага оптимизации кода.
- Сегментация пользовательского интерфейса. Ограничьте отображение пользовательского интерфейса с помощью сегментированных структур. Теперь вы можете хранить структуры, представляющие страницы данных, которые можно быстро применить к функциям отображения. Одиночные непрерывные массивы могут использоваться для отображения отдельных представлений или даже иерархических структур, таких как узлы в TreeView, путем сегментирования линейного хранилища данных на сегменты сбора узлов.
В этом примере мы рассмотрим, как вы можете использовать исходный массив, свойства Offset и Count, а также как вы можете перебирать элементы, указанные в ArraySegment.
using System;
class Program
{
static void Main()
{
// Create an ArraySegment from this array.
int[] array = { 10, 20, 30 };
ArraySegment<int> segment = new ArraySegment<int>(array, 1, 2);
// Write the array.
Console.WriteLine("-- Array --");
int[] original = segment.Array;
foreach (int value in original)
{
Console.WriteLine(value);
}
// Write the offset.
Console.WriteLine("-- Offset --");
Console.WriteLine(segment.Offset);
// Write the count.
Console.WriteLine("-- Count --");
Console.WriteLine(segment.Count);
// Write the elements in the range specified in the ArraySegment.
Console.WriteLine("-- Range --");
for (int i = segment.Offset; i < segment.Count+segment.Offset; i++)
{
Console.WriteLine(segment.Array[i]);
}
}
}
Что насчет класса-обёртки? Просто чтобы избежать копирования данных во временные буферы.
public class SubArray<T> {
private ArraySegment<T> segment;
public SubArray(T[] array, int offset, int count) {
segment = new ArraySegment<T>(array, offset, count);
}
public int Count {
get { return segment.Count; }
}
public T this[int index] {
get {
return segment.Array[segment.Offset + index];
}
}
public T[] ToArray() {
T[] temp = new T[segment.Count];
Array.Copy(segment.Array, segment.Offset, temp, 0, segment.Count);
return temp;
}
public IEnumerator<T> GetEnumerator() {
for (int i = segment.Offset; i < segment.Offset + segment.Count; i++) {
yield return segment.Array[i];
}
}
} //end of the class
Пример:
byte[] pp = new byte[] { 1, 2, 3, 4 };
SubArray<byte> sa = new SubArray<byte>(pp, 2, 2);
Console.WriteLine(sa[0]);
Console.WriteLine(sa[1]);
//Console.WriteLine(b[2]); exception
Console.WriteLine();
foreach (byte b in sa) {
Console.WriteLine(b);
}
Ouput:
3
4
3
4
ArraySegment НАМНОГО более полезен, чем вы думаете. Попробуйте запустить следующий модульный тест и приготовьтесь удивляться!
[TestMethod]
public void ArraySegmentMagic()
{
var arr = new[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
var arrSegs = new ArraySegment<int>[3];
arrSegs[0] = new ArraySegment<int>(arr, 0, 3);
arrSegs[1] = new ArraySegment<int>(arr, 3, 3);
arrSegs[2] = new ArraySegment<int>(arr, 6, 3);
for (var i = 0; i < 3; i++)
{
var seg = arrSegs[i] as IList<int>;
Console.Write(seg.GetType().Name.Substring(0, 12) + i);
Console.Write(" {");
for (var j = 0; j < seg.Count; j++)
{
Console.Write("{0},", seg[j]);
}
Console.WriteLine("}");
}
}
Видите ли, все, что вам нужно сделать, это привести ArraySegment к IList, и он будет делать все то, что вы, вероятно, ожидали, что он сделает в первую очередь. Обратите внимание, что типом по-прежнему является ArraySegment, даже если он ведет себя как обычный список.
ВЫХОД:
ArraySegment0 {0,1,2,}
ArraySegment1 {3,4,5,}
ArraySegment2 {6,7,8,}
Простыми словами: он хранит ссылку на массив, позволяя вам иметь несколько ссылок на одну переменную массива, каждая из которых имеет свой диапазон.
Фактически это помогает вам использовать и передавать разделы массива более структурированным способом, вместо нескольких переменных, для хранения начального индекса и длины. Также он предоставляет интерфейсы коллекций для более удобной работы с разделами массива.
Например, следующие два примера кода делают то же самое, один с ArraySegment, а другой без:
byte[] arr1 = new byte[] { 1, 2, 3, 4, 5, 6 };
ArraySegment<byte> seg1 = new ArraySegment<byte>(arr1, 2, 2);
MessageBox.Show((seg1 as IList<byte>)[0].ToString());
а также,
byte[] arr1 = new byte[] { 1, 2, 3, 4, 5, 6 };
int offset = 2;
int length = 2;
byte[] arr2 = arr1;
MessageBox.Show(arr2[offset + 0].ToString());
Очевидно, что первый фрагмент кода более предпочтителен, особенно когда вы хотите передать сегменты массива в функцию.
Единственный раз, когда я используюArraySegment<byte>
это когда я хочу отправить двоих вместе черезSocket
сSocket.Send(List<ArraySegement<byte>>)
конструкция, вместо соединения двухbyte[]
вместе, потому что это быстрее, чемbyte[].Concat(byte[]).ToArray()
.
Тестовый код:
Stopwatch sw = new Stopwatch();
byte[] a;
byte[] first = {1,2,3,4,5};
byte[] second = {1, 2, 3,4,5};
sw.Start();
a = first.Concat(second).ToArray();
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
var messageSegment = new ArraySegment<byte>(first);
var endSignSegment = new ArraySegment<byte>(second);
var segments = new List<ArraySegment<byte>>() { messageSegment, endSignSegment };
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
Выход:
5
0