In this article, we will show how to use the ArraySegment<T> in C#.
Arrays are one of the most fundamental collection types in C#, but in some cases working with them may not be optimal as far as memory usage is concerned. For example, when we want to work with only a part of the array rather than the whole thing. In this case, we could copy a portion of the original array into a new one. But what if we don’t want to create a new array, but rather work on a segment of the original one? This is where the generic ArraySegment<T> structure comes in handy.
Use Cases for ArraySegment<T>
We use ArraySegment<T>
to create a view of a portion of a one-dimensional array. So, we’re not creating a copy and we’re still working on the same array. Only now we have a discreet portion of the array to operate with.
If you’re interested in memory optimization while working with arrays, make sure to check out our article Memory Optimization with ArrayPool in C#.
An ArraySegment<T>
is useful in several cases. For example, if we have to pass a portion of the array as an argument to a method. Creating a copy of the segment is a rather expensive operation. Passing a view of the segment, on the other hand, is more efficient.
The struct is also useful if we want to work on multiple segments of the array simultaneously. We can then run a separate task for each segment. Using the struct in concurrent processing is one of its most typical use cases.
Now that we know when to use the ArraySegment<T>
struct, let’s see it in action.
Constructors
To instantiate an ArraySegment<T>
, we can use one of two constructors. We use the ArraySegment<T>(T[])
constructor if we want to delimit all the elements in an array. Here the original array is passed as the argument.
We use the other constructor, ArraySegment<T>(T[], int offset, int count)
, to specify the segment of the original array. The first argument is the original array. The second is the starting index or offset of the segment. The last is the number of elements the segment should contain.
Let’s create a simple array and then try out the constructors:
string[] cities = ["Atlanta", "Belgrade", "Warsaw", "Berlin", "Tokyo", "London", "Cairo", "Sydney"]; var segment = new ArraySegment<string>(cities, 2, 5); Console.WriteLine($"Array view: {segment.Count} elements"); var arraySegment = new ArraySegment<string>(cities); Console.WriteLine($"Array view: {arraySegment.Count} elements");
Here we start with an array of strings. First, we create a segment
that contains five elements from the original array, starting at index 2:
Array view: 5 elements
Next, we create a segment arraySegment
that contains all the elements from the original array:
Array view: 8 elements
Now that we know how to create an array segment, we will take a closer look at the methods we can use on it.
Retrieving Elements From An ArraySegment<T>
To retrieve an element from an array segment, we just use the indexer as we would with an Array
or a List<T>
. The arraySegment
we have created contains five elements. So, to retrieve the fourth one, we use the zero-based index of 3:
Console.WriteLine($"Element at index 3: {segment[3]}");
The output is:
Element at index 3: London
Iterating over ArraySegment<T> Elements
Iterating over an array segment is pretty straightforward. We can use the for
loop:
for (int i = 0; i < segment.Count; i++) Console.WriteLine(segment[i]);
Here, we use a standard for-loop iteration over the elements. When we print out the content of our arraySegment
, we will see all five elements in the output:
Warsaw Berlin Tokyo London Cairo
Because ArraySegment<T>
implements the IEnumerable
interface, we can also iterate using a foreach
loop:
foreach (var element in segment) Console.WriteLine(element);
The result is the same as our for loop example.
ArraySegment<T>
also has some useful properties. Let’s have a look at them.
Properties
There is one thing we should be aware of when discussing properties. Since ArraySegment<T>
is just a projection over the underlying array, changes made through the property are made directly to the original array. We’ll discuss the relationships between an array segment and its underlying array in more detail in a later section.
The first property worth mentioning is Array
, which returns the original array:
var originalArray = segment.Array; for (int i = 0; i < originalArray?.Length; i++) Console.WriteLine(originalArray[i]);
The original array in our case is the cities
array, and so as expected, iterating over it returns all of the elements:
Atlanta Belgrade Warsaw Berlin Tokyo London Cairo Sydney
Count
returns the length or count of elements contained in the ArraySegment<T>
:
segment.Count;
In our case, this value is 5.
Offset
returns the position of the first element of the ArraySegment<T>
relative to the start of the original array:
segment.Offset;
The last property we’re discussing here is Empty
. This is a static property that represents an array segment of a specified type with zero elements:
var emptySegment = ArraySegment<string>.Empty;
Next, let’s examine how we can compare two ArraySegment<T>
objects.
Comparing Multiple ArraySegment<T> Objects
It’s possible to create more than one segment of an array. The particular segments may contain the same or different elements. They may fully or partially overlap. The segments can be of identical or different lengths. So, when are two or more segments equal?
To check for equality we use the Equals()
method or the equality (==
) and inequality (!=
) operators. For multiple segments to be equal, three conditions must be met. First, they must all be segments of the same array. Secondly, they must have the same offset from the beginning of the original array. Finally, they must contain the same number of elements.
To demonstrate it, let’s create a couple segments of the cities
array:
var segment1 = new ArraySegment<string>(cities, 2, 5); var segment2 = new ArraySegment<string>(cities, 2, 5); var segment3 = new ArraySegment<string>(cities, 3, 5); Console.WriteLine($"Segments 1 and 2 are equal: {segment1 == segment2}"); Console.WriteLine($"Segments 1 and 3 are equal: {segment1 == segment3}");
In our case above, we have created two ArraySegment<string>
that are equal: segment1
and segment2
. We also created a third ArraySegment<string>
that is not. We then print the results of the comparison to the console:
Segments 1 and 2 are equal: True Segments 1 and 3 are equal: False
Sometimes we might need to create a new segment from our original segment to further partition our work. In this case, we can slice it.
Slicing ArraySegment<T>
To slice an array segment, we use the Slice()
method. The method can take one or two arguments. The first (or sole if we only provide one) argument is the starting index. The second argument is the length of the slice. Let’s create a slice containing all the elements from index 2 to the end:
Console.WriteLine("The segment contains the following elements:"); foreach (var element in segment) Console.Write(element + " "); var slice1 = segment.Slice(2); Console.WriteLine(); Console.WriteLine("Slice 1 contains the following elements:"); foreach (var element in slice1) Console.Write(element + " ");
We see that the slice contains all the elements from the original segment except the first two:
The segment contains the following elements: Warsaw Berlin Tokyo London Cairo Slice 1 contains the following elements: Tokyo London Cairo
Now, let’s create another slice starting at index 2, but only containing 2 elements:
var slice2 = segment.Slice(2, 2); Console.WriteLine("Slice 2 contains the following elements:"); foreach (var element in slice2) Console.Write(element + " ");
This time we see only the middle part of the segment is in the slice:
Slice 2 contains the following elements: Tokyo London
The ArraySegment
struct also supports slicing via the range indexer. For example, segment[1..3]
returns an ArraySegment
of two elements:
var slice3 = segment[1..3]; Console.WriteLine("Slice 3 contains the following elements:"); foreach (var element in slice3) Console.Write(element + " ");
The first index in the range is inclusive, whereas the second one is exclusive:
Slice 3 contains the following elements: Berlin Tokyo
Now let’s examine the relationships between arrays and ArraySegments.
Array – ArraySegment<T> Relationships
We must remember that an array segment is a view of an array or a portion of an array. This means it’s not a copy. So, when we operate on the elements of the segment, we operate on the elements of the original array. Let’s show this through an example:
Console.WriteLine("Original array:"); foreach (var element in cities) Console.Write(element + " "); Console.WriteLine("\nSegment:"); foreach (var element in segment) Console.Write(element + " "); Console.WriteLine("\n\nModifying an element in the segment..."); segment[2] = "Kyoto"; Console.WriteLine("\nModified segment:"); foreach (var element in segment) Console.Write(element + " "); Console.WriteLine("\nOriginal array:"); foreach (var element in cities) Console.Write(element + " ");
Here we print out the contents of the original array, as well as our segment. Then we modify an element of the segment, which we see modifies the original element in the underlying array:
Original array: Atlanta Belgrade Warsaw Berlin Tokyo London Cairo Sydney Segment: Warsaw Berlin Tokyo London Cairo Modifying an element in the segment... Modified segment: Warsaw Berlin Kyoto London Cairo Original array: Atlanta Belgrade Warsaw Berlin Kyoto London Cairo Sydney
The relationship between the indices of the elements in the array and the segment takes the offset into account:
for (int i = segment.Offset; i < segment.Offset + segment.Count; i++) Console.WriteLine(segment.Array?[i]);
In the output, we will get all the elements of the segment:
Warsaw Berlin Kyoto London Cairo
It’s important to remember that even if we have multiple segments and modify their elements, we modify the underlying array. If this is not what we need, we should consider creating copies of the original array.
Conclusion
Now that we know how to use ArraySegment<T>, we understand it is a simple solution for when we want to work with just a portion of an Array
. What we mustn’t forget, is that an ArraySegment<T> is just a view of the original array or its portion. So if we modify the segment, we modify the underlying array. If we want the array to stay intact, we should use a copy of it.