Какая польза от класса ArraySegment <T>?

98

Я только что натолкнулся на этот ArraySegment<byte>тип при создании подкласса MessageEncoder.

Теперь я понимаю, что это сегмент данного массива, принимает смещение, не перечислим и не имеет индексатора, но я все еще не понимаю его использования. Может кто-нибудь объяснить на примере?

stackoverflowuser
источник
8
Похоже, ArraySegmentэто перечислимо в .Net 4.5.
svick
За попытку, подобную этому вопросу ..
Кен Кин

Ответы:

57

ArraySegment<T>стал намного более полезным в .NET 4.5 + и .NET Core, поскольку теперь он реализует:

  • 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
Стивен Кеннеди
источник
4
Хотя они необъяснимо стали GetEnumeratorчастными, то есть вы вынуждены использовать IEnumerable<T>(преобразование бокса), чтобы вызвать это. Ух!
BlueRaja - Дэнни Пфлугхофт
27
  1. Разделение буфера для классов ввода-вывода - используйте один и тот же буфер для одновременных операций чтения и записи и имейте единую структуру, которую вы можете передать, описывая всю вашу операцию.
  2. Функции набора - математически говоря, вы можете представить любые смежные подмножества, используя эту новую структуру. Это в основном означает, что вы можете создавать разделы массива, но не можете представить, скажем, все шансы и все события. Обратите внимание, что телефонный тизер, предложенный The1, можно было бы элегантно решить с помощью разделения ArraySegment и древовидной структуры. Окончательные числа можно было бы записать, сначала пройдя по глубине дерева. Я считаю, что это был бы идеальный сценарий с точки зрения памяти и скорости.
  3. Многопоточность - теперь вы можете создавать несколько потоков для работы с одним и тем же источником данных, используя сегментированные массивы в качестве шлюза управления. Циклы, использующие дискретные вычисления, теперь могут быть довольно легко отданы на откуп, что начинают делать последние компиляторы C ++ в качестве шага оптимизации кода.
  4. Сегментация пользовательского интерфейса - ограничьте отображение пользовательского интерфейса с помощью сегментированных структур. Теперь вы можете хранить структуры, представляющие страницы данных, которые можно быстро применить к функциям отображения. Отдельные непрерывные массивы могут использоваться для отображения дискретных представлений или даже иерархических структур, таких как узлы в 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]);
        }
    }
}

Структура ArraySegment - о чем они думали?

Грег Макналти
источник
3
ArraySegment - это просто структура. Мое лучшее предположение состоит в том, что его цель - позволить передавать сегмент массива без необходимости делать его копию.
Брайан
1
Я считаю, что условие цикла for должно быть i < segment.Offset + segment.Count.
Eren Ersönmez
1
+1 за упомянутые вами факты, но @Eren прав: вы не можете повторять такие элементы сегмента.
afak Gür
3
Обычно уместно указывать авторство, когда вы используете чей-то код. Это просто хорошие манеры. Ваш пример исходит из dotnetperls.com/arraysegment .
1
Если, конечно, не позаимствовали из вашего ответа. В этом случае они должны дать вам кредиты. :)
26

Это маленькая структура солдатика, которая ничего не делает, кроме как хранит ссылку на массив и хранит диапазон индексов. Немного опасно, имейте в виду, что он не делает копию данных массива и никоим образом не делает массив неизменяемым или не выражает необходимость неизменности. Более типичный шаблон программирования - просто сохранить или передать массив и переменную или параметр длины, как это делается в методах .NET BeginRead (), String.SubString (), Encoding.GetString () и т. Д. И т. Д.

Он не находит особого применения внутри .NET Framework, за исключением того, что кажется одним конкретным программистом Microsoft, который работал над веб-сокетами, и WCF это понравилось. Это, вероятно, правильное руководство, если оно вам нравится, используйте его. В .NET 4.6 он действительно выглянул, добавленный метод MemoryStream.TryGetBuffer () его использует. outЯ полагаю, что предпочтительнее иметь два аргумента.

В общем, более универсальное понятие срезов занимает одно из первых мест в списке желаний главных инженеров .NET, таких как Мадс Торгерсен и Стивен Тауб. Последние array[:]некоторое время назад выдвинули предложение по синтаксису, вы можете увидеть, о чем они думали, на этой странице Roslyn . Я предполагаю, что в конечном итоге это зависит от получения поддержки CLR. Это активно обсуждается для C # версии 7 afaik, следите за System.Slices .

Обновление: мертвая ссылка, поставляется в версии 7.2 как Span .

Update2: дополнительная поддержка в C # версии 8.0 с типами Range и Index и методом Slice ().

Ганс Пассан
источник
«Это не очень полезно» - я нашел его невероятно полезным в системе, которая, к сожалению, требовала микрооптимизации из-за ограничения памяти. Тот факт, что существуют и другие «типичные» решения, не умаляет ее полезности
AaronHS
5
Хорошо, хорошо, мне действительно не нужны отзывы от всех, кто привык им пользоваться :) Лучше проголосовать за комментарий @CRice. Как уже отмечалось, «если вам это нравится, то используйте это». Так что используйте это. Кусочки будут классными, не могу дождаться.
Ханс
Для таких неизменных пуристов есть ReadOnlySpan.
Арек Бал
7

Что насчет класса-оболочки? Просто чтобы не копировать данные во временные буферы.

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);
}

Выход:

3
4

3
4
Нергея
источник
Очень полезный приятель, спасибо, обратите внимание, что вы можете реализовать его, а IEnumerable<T>затем добавить IEnumeratorIEnumerable.GetEnumerator() { return GetEnumerator(); }
MaYaN
5

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,}
Бен Стабайл
источник
4
Жалко закидывать надо IList<T>. Я ожидал, что индексатор будет public.
xmedeko
2
Всем, кто приходит к этому ответу и думает, что это чудо-решение, я рекомендую сначала рассмотреть ваши потребности в производительности и сравнить это с прямым доступом к исходному массиву с использованием ограничений индекса из сегмента массива. Приведение к IList требует, чтобы последующие вызовы методов (включая индексатор) выполняли переход через интерфейс IList до достижения реализации. В Интернете много дискуссий, в которых люди говорят о стоимости производительности при использовании абстрактных вызовов в узких циклах. Читайте здесь: github.com/dotnet/coreclr/issues/9105
JamesHoux, 07
3

Проще говоря: он хранит ссылку на массив, что позволяет иметь несколько ссылок на одну переменную массива, каждая из которых имеет свой диапазон.

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

Например, следующие два примера кода делают то же самое, один с 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());

Очевидно, что первый фрагмент кода более предпочтителен, особенно если вы хотите передать сегменты массива функции.

М. Махдипур
источник