Back to Blog
C#

Using ArraySegment in C# to Slice Arrays Without Copying

c# arraysegment usage: Learn how to use ArraySegment<T> in C# to work with slices of arrays without copying data. Understand its API, performance benefits, and when to...

ArraySegmentC# ArraysMemory ManagementSpan<T>PerformanceSlicing
Illustration of ArraySegment<T> representing a slice of an array with offset and count in C#

ArraySegment<T> is a struct in .NET that represents a contiguous range of elements within a T[] array. It lets you pass a slice of an array to methods without creating a new array or copying data. Understanding c# arraysegment usage is useful when you need to work with portions of large arrays efficiently, especially in performance-sensitive code where allocation and copying matter.

What Is ArraySegment<T> and How Does It Work?

ArraySegment<T> wraps three pieces of information: the underlying array, the offset where the segment starts, and the count of elements it covers. It does not copy the array; it simply provides a view over a portion of it. This means changes made through the segment are reflected in the original array, and vice versa.

int[] numbers = { 10, 20, 30, 40, 50 }; ArraySegment<int> segment = new ArraySegment<int>(numbers, 1, 3); // segment covers elements at indices 1, 2, 3: 20, 30, 40

The struct exposes these three pieces via the Array, Offset, and Count properties. You can use them to inspect the segment or to create other segments that share the same underlying array.

Creating ArraySegment Instances

There are several ways to create an ArraySegment<T>. The most common is the explicit constructor that takes an array, an offset, and a count. You can also create a segment that covers the entire array by using the implicit conversion from T[] to ArraySegment<T>. Additionally, a static Empty field provides a reusable empty segment.

int[] numbers = { 10, 20, 30, 40, 50 }; // Explicit: offset 1, count 3 ArraySegment<int> slice = new ArraySegment<int>(numbers, 1, 3); // Implicit conversion from the whole array ArraySegment<int> whole = numbers; // Empty segment ArraySegment<int> empty = ArraySegment<int>.Empty;

The implicit conversion is convenient when you want to pass an entire array to a method that expects an ArraySegment<T> without writing extra code.

Accessing Elements and Iterating Over an ArraySegment

ArraySegment<T> implements IList<T>, ICollection<T>, and IEnumerable<T>, so you can use it with indexers, foreach, and LINQ just like you would with a regular array or list.

int first = slice[0]; // returns 20 foreach (int n in slice) { Console.WriteLine(n); // prints 20, 30, 40 } var sum = slice.Sum(); // LINQ works because it implements IEnumerable<T>

Note that the indexer is relative to the segment, not the underlying array. slice[0] refers to the element at numbers[1]. This is often the behavior you want when you treat the segment as an independent collection.

Passing Slices to Methods and Returning Them

Because ArraySegment<T> implements common collection interfaces, you can pass it to methods that accept IEnumerable<T>, IList<T>, or ICollection<T>. This is useful when you want to process a portion of an array without copying it into a new collection.

void ProcessValues(IList<int> values) { for (int i = 0; i < values.Count; i++) { Console.WriteLine(values[i]); } } int[] data = { 5, 10, 15, 20, 25 }; ArraySegment<int> segment = new ArraySegment<int>(data, 1, 3); ProcessValues(segment); // processes 10, 15, 20

You can also return an ArraySegment<T> from a method. This allows the caller to work with a slice without exposing the entire array. The segment remains valid as long as the underlying array is alive.

ArraySegment<int> GetMiddleThree(int[] source) { return new ArraySegment<int>(source, 1, 3); }

ArraySegment vs. Span<T>: Choosing the Right Slice Type

Span<T> is another type that represents a slice of contiguous memory, but it has different constraints. Span<T> is a ref struct, which means it can only live on the stack. It cannot be used as a field in a class, cannot be captured in a lambda or async method, and cannot be boxed. ArraySegment<T> is a regular struct, so it can be stored in fields, used in async methods, and passed across await boundaries.

Use ArraySegment<T> when you need a slice that must persist beyond a synchronous method call, or when you need to store it in a class. Use Span<T> when you are working in a tight synchronous loop and want maximum performance, especially if you also need to work with unmanaged memory or stack-allocated buffers.

Both types avoid copying the underlying data. The main tradeoff is flexibility versus performance. Span<T> offers more efficient access and can point to memory that is not managed, but it is restricted in where it can appear. ArraySegment<T> is more versatile but adds a small overhead because it is a struct with three fields.

Performance and Memory Behavior

Using ArraySegment<T> avoids the cost of allocating a new array and copying elements. This is especially important when working with large arrays or when slicing happens frequently. The struct itself is small (an array reference, an int offset, and an int count), so passing it by value is cheap.

However, because ArraySegment<T> holds a reference to the original array, it prevents that array from being garbage collected as long as the segment is in use. If you create many segments that reference a large array and then discard the original reference, the array remains alive as long as any segment exists. This is usually acceptable, but you should be aware of it in long-lived scenarios.

Another subtle point: ArraySegment<T> is a struct, so when you pass it to a method, you are passing a copy of the struct itself. The copy still points to the same underlying array, so modifications through the segment affect the original data. But if you reassign the segment's Array, Offset, or Count properties, you are only changing the copy, not the original segment.

Common Pitfalls and Limitations

One common mistake is assuming that ArraySegment<T> has a parameterless constructor that creates a segment over a default array. In reality, the parameterless constructor returns a segment with Array set to null and Offset and Count set to zero. Accessing such a segment throws a NullReferenceException. Always use ArraySegment<T>.Empty or an explicit array.

Another limitation is that ArraySegment<T> does not provide a way to change the length of the underlying array. It is purely a view. If you need to resize the array, you must create a new array and copy data, which defeats the purpose of using a segment.

Also, not all APIs accept ArraySegment<T> directly. Some methods expect T[] or Memory<T>. In those cases, you may need to convert using segment.ToArray() (which copies) or use segment.AsMemory() if you are targeting .NET Core 2.1 or later. Be mindful of the copying cost when using ToArray().

Finally, ArraySegment<T> implements IList<T>, but it does not support adding or removing elements. The Add, Insert, and Remove methods throw NotSupportedException. This is expected because the segment is a fixed-size view. Use it only for read-only or in-place modification scenarios.

Understanding these limitations helps you decide when ArraySegment<T> is the right tool and when a different type like Span<T> or Memory<T> would be more appropriate.

c# arraysegment usage: Slice Arrays Without Copying | RYUSLOG DEV