What is the use of the ArraySegment<T> class?
Asked Answered
D

7

129

I just came across the ArraySegment<byte> type while subclassing the MessageEncoder class.

I now understand that it's a segment of a given array, takes an offset, is not enumerable, and does not have an indexer, but I still fail to understand its usage. Can someone please explain with an example?

Darbydarce answered 5/1, 2011 at 1:12 Comment(2)
It looks like ArraySegment is enumerable in .Net 4.5.Interventionist
For attempt like this question ..Quartermaster
G
71

ArraySegment<T> has become a lot more useful in .NET 4.5+ and .NET Core as it now implements:

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

as opposed to the .NET 4 version which implemented no interfaces whatsoever.

The class is now able to take part in the wonderful world of LINQ so we can do the usual LINQ things like query the contents, reverse the contents without affecting the original array, get the first item, and so on:

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
Gleeman answered 26/10, 2011 at 18:15 Comment(1)
Though they inexplicably made GetEnumerator private, meaning you're forced to cast to IEnumerable<T> (a boxing conversion) to call it. Ugh!Burlesque
B
45

It is a puny little soldier struct that does nothing but keep a reference to an array and stores an index range. A little dangerous, beware that it does not make a copy of the array data and does not in any way make the array immutable or express the need for immutability. The more typical programming pattern is to just keep or pass the array and a length variable or parameter, like it is done in the .NET BeginRead() methods, String.SubString(), Encoding.GetString(), etc, etc.

It does not get much use inside the .NET Framework, except for what seems like one particular Microsoft programmer that worked on web sockets and WCF liking it. Which is probably the proper guidance, if you like it then use it. It did do a peek-a-boo in .NET 4.6, the added MemoryStream.TryGetBuffer() method uses it. Preferred over having two out arguments I assume.

In general, the more universal notion of slices is high on the wishlist of principal .NET engineers like Mads Torgersen and Stephen Toub. The latter kicked off the array[:] syntax proposal a while ago, you can see what they've been thinking about in this Roslyn page. I'd assume that getting CLR support is what this ultimately hinges on. This is actively being thought about for C# version 7 afaik, keep your eye on System.Slices.

Update: dead link, this shipped in version 7.2 as Span.

Update2: more support in C# version 8.0 with Range and Index types and a Slice() method.

Backstretch answered 5/1, 2011 at 4:30 Comment(4)
"It is not very useful' - I found it incredibly useful in a system that unfortunately required micro optimizations due to memory limitation. The fact that there are also other "typical" solutions does not detract from its utilityKatiekatina
Okay, okay, I don't really need a testimonial from everybody that got in the habit of using it :) Best to upvote @CRice's comment. As noted, "if you like it then use it". So use it. Slices will be awesome, can't wait.Backstretch
There is a ReadOnlySpan for those immutable purists out there.Rambler
ArraySegment has indexer, while Memory and Span are dead silent about it.Adelleadelpho
P
28
  1. Buffer partioning for IO classes - Use the same buffer for simultaneous read and write operations and have a single structure you can pass around the describes your entire operation.
  2. Set Functions - Mathematically speaking you can represent any contiguous subsets using this new structure. That basically means you can create partitions of the array, but you can't represent say all odds and all evens. Note that the phone teaser proposed by The1 could have been elegantly solved using ArraySegment partitioning and a tree structure. The final numbers could have been written out by traversing the tree depth first. This would have been an ideal scenario in terms of memory and speed I believe.
  3. Multithreading - You can now spawn multiple threads to operate over the same data source while using segmented arrays as the control gate. Loops that use discrete calculations can now be farmed out quite easily, something that the latest C++ compilers are starting to do as a code optimization step.
  4. UI Segmentation - Constrain your UI displays using segmented structures. You can now store structures representing pages of data that can quickly be applied to the display functions. Single contiguous arrays can be used in order to display discrete views, or even hierarchical structures such as the nodes in a TreeView by segmenting a linear data store into node collection segments.

In this example, we look at how you can use the original array, the Offset and Count properties, and also how you can loop through the elements specified in the 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 - what were they thinking?

Pompea answered 5/1, 2011 at 1:24 Comment(6)
ArraySegment is just a structure. My best guess is that its purpose is to allow a segment of an array to be passed around without having to make a copy of it.Caddish
I believe the condition statement of the for loop should be i < segment.Offset + segment.Count.Suckle
+1 for the facts you mentioned but @Eren is right: You can't iterate a segment's elements like that.Devin
The "what were they thinking" article is nothing but an ill-informed rant.Untread
It's usually appropriate to give attribution when you use someone elses code. It's just good manners. Your example originates from dotnetperls.com/arraysegment.Drew
Unless of course, they borrowed it from your answer. In which case, they should give you creds. :)Drew
J
6

What's about a wrapper class? Just to avoid copy data to temporal buffers.

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

Example:

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
Joung answered 25/10, 2012 at 15:41 Comment(1)
Very useful buddy, thanks, note you can make it implement IEnumerable<T> then add IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }Adman
C
5

The ArraySegment is MUCH more useful than you might think. Try running the following unit test and prepare to be amazed!

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

You see, all you have to do is cast an ArraySegment to IList and it will do all of the things you probably expected it to do in the first place. Notice that the type is still ArraySegment, even though it is behaving like a normal list.

OUTPUT:

ArraySegment0 {0,1,2,}
ArraySegment1 {3,4,5,}
ArraySegment2 {6,7,8,}
Crocein answered 19/5, 2015 at 23:29 Comment(3)
It's pity it's necessary to cast it to IList<T>. I would expect the indexer to be public.Regurgitation
For anyone who comes upon this answer and thinks its a miracle solution, I recommend first considering your performance needs and benchmark this compared to direct access to the original array using the index constraints from the array segment. Casting to an IList requires subsequent method calls (including the indexer) to jump through the IList interface before reaching the implementation. There are a lot of discussions on the internet where people talk about the performance cost of using abstracted calls in tight loops. Read here: github.com/dotnet/coreclr/issues/9105Hardback
ArraySegment's indexer is now public (as it arguably should have been from the beginning).Sharice
C
5

In simple words: it keeps reference to an array, allowing you to have multiple references to a single array variable, each one with a different range.

In fact it helps you to use and pass sections of an array in a more structured way, instead of having multiple variables, for holding start index and length. Also it provides collection interfaces to work more easily with array sections.

For example the following two code examples do the same thing, one with ArraySegment and one without:

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

and,

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

Obviously first code snippet is more preferred, specially when you want to pass array segments to a function.

Cratch answered 3/5, 2018 at 10:51 Comment(0)
S
0

The only time I use ArraySegment<byte> is when I want to send two byte[] together through Socket with the Socket.Send(List<ArraySegement<byte>>) constructure, instead of connecting two byte[] together, because it is faster than byte[].Concat(byte[]).ToArray().


Test code:

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

Output:

5
0
Syllabize answered 29/10, 2023 at 2:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.