Для чого потрібен клас ArraySegment <T>?


97

Я щойно натрапив на ArraySegment<byte>тип, підкласуючи MessageEncoderклас.

Зараз я розумію, що це сегмент даного масиву, виконує зміщення, не перелічується і не має індексатора, але я все ще не розумію його використання. Хтось може пояснити на прикладі?


8
Схоже, ArraySegmentце перелічено в .Net 4.5.
svick

За спробу, як це запитання ..
Ken Kin

Відповіді:


55

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 - Danny Pflughoeft

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 Structure - про що вони думали?


3
ArraySegment - це просто структура. Я найкраще здогадуюсь, що його мета - дозволити передавати сегмент масиву без необхідності робити його копію.
Брайан

1
Я вважаю, що має бути твердження умови циклу for i < segment.Offset + segment.Count.
Eren Ersönmez

1
+1 за факти, про які ви згадали, але @Eren має рацію: ви не можете повторювати елементи сегмента так.
faafak 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

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

Очевидно, що перший фрагмент коду є кращим, особливо коли ви хочете передати сегменту масиву функції.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.