Apa gunanya kelas ArraySegment <T>?

98

Saya baru saja menemukan ArraySegment<byte>tipe saat membuat subclass MessageEncoderkelas.

Sekarang saya mengerti bahwa itu adalah segmen dari larik yang diberikan, mengambil offset, tidak dapat dihitung, dan tidak memiliki pengindeks, tetapi saya masih gagal memahami penggunaannya. Bisakah seseorang menjelaskan dengan sebuah contoh?

stackoverflowuser
sumber
8
Sepertinya ArraySegmentdapat dihitung dalam .Net 4.5.
svick
Untuk percobaan seperti pertanyaan ini ..
Ken Kin

Jawaban:

57

ArraySegment<T>telah menjadi jauh lebih berguna di .NET 4.5 + dan .NET Core karena sekarang diimplementasikan:

  • IList<T>
  • ICollection<T>
  • IEnumerable<T>
  • IEnumerable
  • IReadOnlyList<T>
  • IReadOnlyCollection<T>

berlawanan dengan versi .NET 4 yang tidak menerapkan antarmuka apa pun.

Kelas sekarang dapat mengambil bagian dalam dunia LINQ yang menakjubkan sehingga kita dapat melakukan hal-hal LINQ yang biasa seperti meminta konten, membalikkan konten tanpa memengaruhi larik asli, mendapatkan item pertama, dan seterusnya:

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
Stephen Kennedy
sumber
4
Meskipun mereka secara tidak dapat dijelaskan dibuat GetEnumeratorpribadi, artinya Anda terpaksa melakukan IEnumerable<T>(konversi tinju) untuk menyebutnya. Ugh!
BlueRaja - Danny Pflughoeft
27
  1. Partioning buffer untuk kelas IO - Gunakan buffer yang sama untuk operasi baca dan tulis secara bersamaan dan memiliki struktur tunggal yang dapat Anda berikan untuk mendeskripsikan seluruh operasi Anda.
  2. Fungsi Himpunan - Secara matematis Anda dapat merepresentasikan himpunan bagian yang berdekatan menggunakan struktur baru ini. Itu pada dasarnya berarti Anda dapat membuat partisi dari array, tetapi Anda tidak dapat menyatakan semua peluang dan semua genap. Perhatikan bahwa teaser telepon yang diusulkan oleh The1 dapat diselesaikan dengan elegan menggunakan partisi ArraySegment dan struktur pohon. Angka akhir bisa saja ditulis dengan menelusuri kedalaman pohon terlebih dahulu. Ini akan menjadi skenario yang ideal dalam hal memori dan kecepatan.
  3. Multithreading - Anda sekarang dapat menelurkan beberapa utas untuk beroperasi pada sumber data yang sama sambil menggunakan larik tersegmentasi sebagai gerbang kontrol. Loop yang menggunakan kalkulasi terpisah sekarang dapat di-farmed out dengan cukup mudah, sesuatu yang mulai dilakukan oleh compiler C ++ terbaru sebagai langkah pengoptimalan kode.
  4. Segmentasi UI - Batasi tampilan UI Anda menggunakan struktur tersegmentasi. Anda sekarang dapat menyimpan struktur yang mewakili halaman data yang dapat dengan cepat diterapkan ke fungsi tampilan. Larik bersebelahan tunggal dapat digunakan untuk menampilkan tampilan diskrit, atau bahkan struktur hierarki seperti node di TreeView dengan membagi penyimpanan data linier ke dalam segmen kumpulan node.

Dalam contoh ini, kita melihat bagaimana Anda dapat menggunakan array asli, properti Offset dan Count, dan juga bagaimana Anda dapat melakukan loop melalui elemen yang ditentukan dalam 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]);
        }
    }
}

Struktur ArraySegment - apa yang mereka pikirkan?

Greg McNulty
sumber
3
ArraySegment hanyalah sebuah struktur. Dugaan terbaik saya adalah bahwa tujuannya adalah untuk memungkinkan segmen array untuk diedarkan tanpa harus membuat salinannya.
Brian
1
Saya percaya pernyataan kondisi loop for seharusnya i < segment.Offset + segment.Count.
Eren Ersönmez
1
+1 untuk fakta yang Anda sebutkan tetapi @Eren benar: Anda tidak dapat mengulang elemen segmen seperti itu.
Şafak Gür
3
Biasanya tepat untuk memberikan atribusi saat Anda menggunakan kode orang lain. Itu hanya sopan santun. Contoh Anda berasal dari dotnetperls.com/arraysegment .
1
Kecuali, tentu saja, mereka meminjamnya dari jawaban Anda. Dalam hal ini, mereka harus memberi Anda kredibilitas. :)
26

Ini adalah struct prajurit kecil yang lemah yang tidak melakukan apa pun selain menyimpan referensi ke array dan menyimpan rentang indeks. Sedikit berbahaya, berhati-hatilah karena ini tidak membuat salinan data larik dan tidak dengan cara apa pun membuat larik tidak dapat diubah atau menyatakan kebutuhan akan kekekalan. Pola pemrograman yang lebih umum adalah dengan hanya menyimpan atau meneruskan array dan variabel panjang atau parameter, seperti yang dilakukan dalam metode .NET BeginRead (), String.SubString (), Encoding.GetString (), dll, dll.

Itu tidak banyak digunakan di dalam .NET Framework, kecuali untuk apa yang tampak seperti salah satu pemrogram Microsoft tertentu yang bekerja pada soket web dan WCF menyukainya. Yang mungkin panduan yang tepat, jika Anda suka maka gunakanlah. Itu melakukan peek-a-boo di .NET 4.6, metode MemoryStream.TryGetBuffer () yang ditambahkan menggunakannya. Lebih disukai daripada memiliki dua outargumen yang saya asumsikan.

Secara umum, pengertian slice yang lebih universal ada di daftar keinginan para engineer .NET utama seperti Mads Torgersen dan Stephen Toub. Yang terakhir memulai array[:]proposal sintaks beberapa waktu yang lalu, Anda dapat melihat apa yang mereka pikirkan di halaman Roslyn ini . Saya berasumsi bahwa mendapatkan dukungan CLR adalah apa yang akhirnya bergantung pada hal ini. Ini sedang dipikirkan secara aktif untuk C # versi 7 afaik, awasi System.Slices .

Pembaruan: tautan mati, ini dikirim dalam versi 7.2 sebagai Span .

Update2: lebih banyak dukungan dalam C # versi 8.0 dengan tipe Range dan Index dan metode Slice ().

Hans Passant
sumber
"Ini tidak terlalu berguna '- Saya merasa sangat berguna dalam sistem yang sayangnya memerlukan pengoptimalan mikro karena keterbatasan memori. Fakta bahwa ada juga solusi" khas "lainnya tidak mengurangi kegunaannya
AaronHS
5
Oke, oke, saya tidak terlalu membutuhkan testimonial dari semua orang yang terbiasa menggunakannya :) Best to upvote @ CRice's comment. Seperti disebutkan, "jika Anda suka maka gunakanlah". Jadi gunakan itu. Irisan akan luar biasa, tidak sabar.
Hans Passant
Ada ReadOnlySpan untuk para puritan abadi di luar sana.
Arek Bal
7

Bagaimana dengan kelas pembungkus? Hanya untuk menghindari menyalin data ke buffer sementara.

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

Contoh:

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
nergeia
sumber
Sobat yang sangat berguna, terima kasih, perhatikan bahwa Anda dapat menerapkannya IEnumerable<T>kemudian menambahkan IEnumeratorIEnumerable.GetEnumerator() { return GetEnumerator(); }
MaYaN
5

ArraySegment JAUH lebih berguna dari yang Anda kira. Coba jalankan unit test berikut dan bersiaplah untuk kagum!

    [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("}");
        }
    }

Soalnya, yang harus Anda lakukan adalah melemparkan ArraySegment ke IList dan itu akan melakukan semua hal yang mungkin Anda harapkan dari awalnya. Perhatikan bahwa tipenya masih ArraySegment, meskipun berperilaku seperti daftar normal.

KELUARAN:

ArraySegment0 {0,1,2,}
ArraySegment1 {3,4,5,}
ArraySegment2 {6,7,8,}
Ben Stabile
sumber
4
Sangat disayangkan itu perlu untuk dilemparkan ke IList<T>. Saya berharap pengindeks akan menjadi public.
xmedeko
2
Bagi siapa pun yang mendapatkan jawaban ini dan menganggapnya sebagai solusi ajaib, saya sarankan untuk terlebih dahulu mempertimbangkan kebutuhan kinerja Anda dan tolok ukur ini dibandingkan dengan akses langsung ke larik asli menggunakan batasan indeks dari segmen larik. Mentransmisikan ke IList memerlukan panggilan metode berikutnya (termasuk pengindeks) untuk melewati antarmuka IList sebelum mencapai implementasi. Ada banyak diskusi di internet di mana orang berbicara tentang biaya kinerja menggunakan panggilan abstrak dalam loop yang ketat. Baca di sini: github.com/dotnet/coreclr/issues/9105
JamesHoux
3

Dengan kata sederhana: ini menyimpan referensi ke sebuah array, memungkinkan Anda memiliki banyak referensi ke satu variabel array, masing-masing dengan rentang yang berbeda.

Sebenarnya ini membantu Anda untuk menggunakan dan melewatkan bagian dari sebuah array dengan cara yang lebih terstruktur, daripada memiliki banyak variabel, untuk menahan indeks awal dan panjang. Juga menyediakan antarmuka koleksi untuk bekerja lebih mudah dengan bagian array.

Misalnya, dua contoh kode berikut melakukan hal yang sama, satu dengan ArraySegment dan satu tanpa:

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

dan,

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

Jelas potongan kode pertama lebih disukai, khususnya ketika Anda ingin meneruskan segmen array ke suatu fungsi.

M. Mahdipour
sumber