Wimdows.NET

Wim's .NET blog

ArraySegment Structure - what were they thinking?

From the MSDN docs:
ArraySegment is a wrapper around an array that delimits a range of elements in that array. Multiple ArraySegment instances can refer to the same original array and can overlap.

Turns out this structure doesn't even deserve the definition 'wrapper'. It simply takes the array, offset and number of elements in your segment, and sets a few properties accordingly.

Subsequently when you want to iterate over the items in your ArraySegment, you still need to use a combination of Offset and Count to achieve this. How is this different from not creating an ArraySegment and define your Offset in-situ as well as the number of elements?

I was expecting to be able to do something like this:

ArraySegment<string> seg = new ArraySegment<string>(new string[] { "John","Jack","Jill","Joe"},1,2);
// Access first item in segment
string first = seg[0];
// Iterate through ArraySegment
foreach (string s in seg)
{
    Console.WriteLine(s);
}


Turns out you can't. There's no indexer for ArraySegment and no enumerator. You have to access the .Array property and use .Count and .Offset as passed to the constructor. What is the point of that!?

So I rolled my own generic DelimitedArray class which does exactly that. See the inline code below.

    public class DelimitedArray<T>
    {
        public DelimitedArray(T[] array, int offset, int count)
        {
            this._array = array;
            this._offset = offset;
            this._count = count;
        }

        private int _offset;
        private T[] _array;

        private int _count;
        public int Count
        {
            get { return this._count; }
        }

        public T this[int index]
        {
            get
            {
                int idx = this._offset + index;
                if (idx > this.Count - 1 || idx<0)
                {
                    throw new IndexOutOfRangeException("Index '" + idx + "' was outside the bounds of the array.");
                }
                return this._array[idx];
            }
        }

        public IEnumerator<T> GetEnumerator()
        {
            for (int i = this._offset; i < this._offset + this.Count; i++)
            {
                yield return this._array[i];
            }
        }
    }


Hope this is of use to someone.
Posted: Jun 14 2006, 03:24 PM by Wim | with 18 comment(s)
Filed under: , , ,

Comments

Gabriel Lozano-Morán said:

It is correctly named a 'wrapper' you were propable expecting a 'decorator'.


# June 14, 2006 8:40 PM

Wim said:

Gabriel - the main point I'm making is, the ArraySegment 'wrapper' is totally useless!!! Which is why I rolled the DelimitedArray<T> class.
# June 15, 2006 9:30 AM

Richard said:

It looks like your indexer is off by one:

int idx = this._offset + index - 1;

If offset is 1 (starting from the second element of the array) and index is zero (returning the first element of the section), you will return the first element of the array, not the second.

# July 6, 2006 9:12 AM

Wim said:

Good catch Richard!

Fixed now...

# August 21, 2006 11:21 AM

William said:

It is more of a container class to allow passing Lists of ArraySegments to a method.  For example, the Socket class now allows passing ArraySegments so you can get scatter/gather semantics.  You could not pass parcial buffers without something like ArraySegment and would need to copy to buffer of exact size. It is probably most useful for byte[]s, but it did not really cost anything to make it generic - so I guess that why it is generic.

# August 29, 2006 6:31 PM

Martin Marconcini said:

However, I had the exact same problem... and had to roll my own DelimitedArray<T>, which looks very much like Wim's.

# August 30, 2006 4:53 AM

D Patel said:

How is this faster than the base .NET implementation? Sure, it is convenient to use, but the very first issue I see is you declared this as a "class" as opposed to .NET's implementation which declares it as a "struct."

# August 3, 2007 12:06 PM

greyson said:

I doubt there may be bug in you ArraySegment.

If the size of the intenal array is 10 and you create a ArraySegment object with Count 12, what may happen?

# November 15, 2008 9:37 AM

Naeron said:

Thanks, a had same problem

# December 10, 2008 3:23 AM

Michael C said:

Shouldn't this class Implement IEnumerable?

Also, it would be quicker if you don't check to make sure the index into the array is valid (where you throw the exception) as the wrapped array will be doing this check for you a second time.

# December 18, 2008 5:55 PM

daniel said:

You have a tough crowd you are trying to please, judging from some of the comments.

I too think that there are already thousands of vocabulary words to remember when dealing with dot net and C#.  A program has to learn more new words during their education than a foreign language student.  One has to ask if it was really required to add another nonsense word in order to add this functionality.

I agree with you.  It is complication that is not worth it.

# July 7, 2009 8:54 PM

Andreas said:

By changing T[] into IEnumerable<T> and changing this._array[idx] into this._array.ElementAt(idx) you can actually pass almost any list or array into it and still get your segment/part/delimited array of elements.

# August 11, 2010 2:32 PM

MattA said:

I had to add The non-generic GetEnumerator as well to get this to work:

IEnumerator IEnumerable.GetEnumerator()

{

return ((IEnumerable<T>)this).GetEnumerator();

}

# January 30, 2011 8:59 PM

Mike Rosenblum said:

Awesome article, you really nailed it.

# April 9, 2011 11:41 AM

someone said:

let's say you want to pass a function multiple arrays and offsets and lengths... look for example socket.BeginSend: you don't want to copy all the values to a new array because it spends time and memory, therefore, you send List of ArraySegments.

# April 17, 2011 5:11 PM

Darren said:

Looks like the finally fixed it in .NET 4.5!

# September 21, 2011 1:16 PM