Jakie jest zastosowanie klasy ArraySegment <T>?

98

Właśnie natknąłem się na ten ArraySegment<byte>typ podczas tworzenia podklasy MessageEncoder.

Teraz rozumiem, że jest to segment danej tablicy, przyjmuje przesunięcie, nie jest wyliczalny i nie ma indeksatora, ale nadal nie rozumiem jego użycia. Czy ktoś mógłby wyjaśnić na przykładzie?

stackoverflowuser
źródło
8
Wygląda na to, że ArraySegmentjest wyliczalny w .Net 4.5.
svick
Na próbę taką jak to pytanie
Ken Kin

Odpowiedzi:

57

ArraySegment<T>stało się dużo bardziej przydatne w .NET 4.5 + i .NET Core, ponieważ teraz implementuje:

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

w przeciwieństwie do wersji .NET 4, która nie implementowała żadnych interfejsów.

Klasa może teraz uczestniczyć w cudownym świecie LINQ, dzięki czemu możemy wykonywać zwykłe czynności LINQ, takie jak przeszukiwanie zawartości, odwracanie zawartości bez wpływu na oryginalną tablicę, pobranie pierwszego elementu i tak dalej:

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
źródło
4
Chociaż w niewytłumaczalny sposób stały się GetEnumeratorprywatne, co oznacza, że ​​musisz rzucić na IEnumerable<T>(konwersję bokserską), aby to nazwać. Fuj!
BlueRaja - Danny Pflughoeft
27
  1. Partycjonowanie bufora dla klas IO - używaj tego samego bufora do jednoczesnych operacji odczytu i zapisu i uzyskaj jedną strukturę, którą możesz przekazać opisując całą operację.
  2. Funkcje zestawów - mówiąc matematycznie, możesz reprezentować dowolne ciągłe podzbiory za pomocą tej nowej struktury. Zasadniczo oznacza to, że możesz tworzyć partycje tablicy, ale nie możesz reprezentować, powiedzmy, wszystkich szans i wszystkich parzystych. Zauważ, że teaser telefonu zaproponowany przez The1 mógł zostać elegancko rozwiązany przy użyciu partycjonowania ArraySegment i struktury drzewa. Ostateczne liczby można było zapisać, przechodząc najpierw przez głębokość drzewa. To byłby idealny scenariusz pod względem pamięci i szybkości, jak sądzę.
  3. Wielowątkowość - można teraz tworzyć wiele wątków do działania na tym samym źródle danych, używając tablic segmentowanych jako bramy sterującej. Pętle, które używają dyskretnych obliczeń, można teraz dość łatwo wyłowić, co najnowsze kompilatory C ++ zaczynają robić jako krok optymalizacji kodu.
  4. Segmentacja interfejsu użytkownika - ogranicz wyświetlanie interfejsu użytkownika za pomocą segmentowanych struktur. Możesz teraz przechowywać struktury reprezentujące strony danych, które można szybko zastosować do funkcji wyświetlania. Pojedyncze ciągłe tablice mogą być używane do wyświetlania dyskretnych widoków, a nawet struktur hierarchicznych, takich jak węzły w TreeView, poprzez segmentację liniowego magazynu danych na segmenty kolekcji węzłów.

W tym przykładzie przyjrzymy się, jak można użyć oryginalnej tablicy, właściwości Offset i Count, a także jak można przechodzić przez elementy określone w 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 - o czym myśleli?

Greg McNulty
źródło
3
ArraySegment to tylko struktura. Domyślam się, że jego celem jest umożliwienie przekazywania segmentu tablicy bez konieczności tworzenia jej kopii.
Brian,
1
Uważam, że instrukcja warunku pętli for powinna być i < segment.Offset + segment.Count.
Eren Ersönmez
1
+1 za fakty, o których wspomniałeś, ale @Eren ma rację: nie możesz iterować elementów segmentu w ten sposób.
Şafak Gür
3
Jeśli używasz kodu innej osoby, zwykle należy podać źródło. To tylko dobre maniery. Twój przykład pochodzi z dotnetperls.com/arraysegment .
1
Chyba że pożyczyli to z twojej odpowiedzi. W takim przypadku powinni dać ci kredyty. :)
26

Jest to słaba struktura małego żołnierza, która nic nie robi, ale utrzymuje odniesienie do tablicy i przechowuje zakres indeksu. Trochę niebezpieczne, uważaj, że nie tworzy kopii danych tablicy iw żaden sposób nie czyni tablicy niezmienną ani nie wyraża potrzeby niezmienności. Bardziej typowym wzorcem programowania jest po prostu zachowanie lub przekazanie tablicy i zmiennej lub parametru długości, tak jak ma to miejsce w metodach .NET BeginRead (), String.SubString (), Encoding.GetString () itp.

Nie ma większego zastosowania w .NET Framework, z wyjątkiem tego, co wydaje się być jednym konkretnym programistą firmy Microsoft, który pracował na gniazdach internetowych i lubił go WCF. Co jest prawdopodobnie właściwą wskazówką, jeśli ci się spodoba, użyj jej. Zrobił peek-a-boo w .NET 4.6, dodana metoda MemoryStream.TryGetBuffer () używa go. outZakładam, że wolę mieć dwa argumenty.

Ogólnie rzecz biorąc, bardziej uniwersalne pojęcie plasterków jest wysoko na liście życzeń głównych inżynierów .NET, takich jak Mads Torgersen i Stephen Toub. Ten ostatni uruchomił array[:]propozycję składni jakiś czas temu, możesz zobaczyć, o czym myśleli na tej stronie Roslyn . Zakładam, że to ostatecznie zależy od uzyskania obsługi CLR. Jest to aktywnie rozważane w przypadku języka C # w wersji 7 afaik, miej oko na System.Slices .

Aktualizacja: martwy link, ten dostarczany w wersji 7.2 jako Span .

Update2: większa obsługa w języku C # w wersji 8.0 z typami Range i Index oraz metodą Slice ().

Hans Passant
źródło
„To nie jest zbyt użyteczne” - uznałem to za niezwykle przydatne w systemie, który niestety wymagał mikrooptymalizacji z powodu ograniczenia pamięci. Fakt, że istnieją również inne „typowe” rozwiązania, nie umniejsza jego użyteczności
AaronHS
5
Okej, okej, tak naprawdę nie potrzebuję referencji od wszystkich, którzy mają w zwyczaju jej używać :) Najlepiej zagłosować za komentarzem @ CRice. Jak wspomniano, „jeśli ci się spodoba, użyj go”. Więc użyj go. Plasterki będą niesamowite, nie mogę się doczekać.
Hans Passant
Istnieje ReadOnlySpan dla niezmiennych purystów.
Arek Bal,
7

A co z klasą opakowującą? Tylko po to, aby uniknąć kopiowania danych do buforów czasowych.

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

Przykład:

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
źródło
Bardzo przydatny kolego, dzięki, uwaga, możesz to zaimplementować, IEnumerable<T>a następnie dodać IEnumeratorIEnumerable.GetEnumerator() { return GetEnumerator(); }
MaYaN
5

ArraySegment jest ZNACZNIE bardziej przydatny, niż mogłoby się wydawać. Spróbuj przeprowadzić następujący test jednostkowy i przygotuj się na zaskoczenie!

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

Widzisz, wszystko, co musisz zrobić, to rzucić ArraySegment na IList, a on wykona wszystkie rzeczy, których prawdopodobnie oczekiwałeś. Zwróć uwagę, że typem nadal jest ArraySegment, mimo że zachowuje się jak zwykła lista.

WYNIK:

ArraySegment0 {0,1,2,}
ArraySegment1 {3,4,5,}
ArraySegment2 {6,7,8,}
Ben Stabile
źródło
4
Szkoda, że ​​trzeba to rzucić IList<T>. Spodziewałbym się, że indeksator będzie public.
xmedeko
2
Dla każdego, kto natrafi na tę odpowiedź i uważa, że ​​jest to cudowne rozwiązanie, polecam najpierw rozważyć swoje potrzeby dotyczące wydajności i porównać to z bezpośrednim dostępem do oryginalnej tablicy przy użyciu ograniczeń indeksu z segmentu tablicy. Rzutowanie na IList wymaga kolejnych wywołań metod (w tym indeksatora), aby przejść przez interfejs IList przed osiągnięciem implementacji. W Internecie toczy się wiele dyskusji, w których ludzie mówią o kosztach wydajności korzystania z abstrakcyjnych połączeń w ciasnych pętlach. Przeczytaj tutaj: github.com/dotnet/coreclr/issues/9105
JamesHoux,
3

W prostych słowach: zachowuje odniesienie do tablicy, pozwalając na wiele odniesień do jednej zmiennej tablicowej, z których każde ma inny zakres.

W rzeczywistości pomaga to używać i przekazywać sekcje tablicy w bardziej ustrukturyzowany sposób, zamiast posiadania wielu zmiennych, do przechowywania indeksu początkowego i długości. Zapewnia również interfejsy kolekcji, które ułatwiają pracę z sekcjami tablic.

Na przykład poniższe dwa przykłady kodu robią to samo, jeden z ArraySegment, a drugi bez:

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

i,

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

Oczywiście pierwszy fragment kodu jest bardziej preferowany, szczególnie gdy chcesz przekazać do funkcji segmenty tablicy.

M.Mahdipour
źródło