Best Solution In .NET For Slicing Arrays

Best Solution For Slicing Arrays Banner Image

Introduction

The C# array slice methods I'm going to look at are ArraySegment, Span Slice, Range Operator, and LINQ Skip Take. Each of these has pros and cons which I will get into and also test the performance of each. Slicing arrays is an important topic since a lot of data is array-based and we may not want all the data in an array but different portions of it. Some common examples of array slices could be string related since strings are an array of characters. Another place could be file-based CSV parsing and file IO operations or even parsing data from a database. We're going to start with ArraySegment for our first analysis.

Video With Examples

Summary Table

Analysis TypeArraySegmentSpanRange OperatorLINQ Skip Take
.NET Version Available>= .NET 2.0>= .NET Core 2.1>= C#8.0 >= NET 3.5
Lines Of Code1111
Copies ArrayNoYesNoNo

ArraySegment Method For Slicing Arrays

ArraySegment has been around for a long time and has been available since .NET 2.0. It doesn't make a copy of the array but works from the original reference and must be a one-dimensional array

ArraySegment Video Example

ArraySegment Code Example

We can pass the array into the ArraySegment constructor and also give the start and length of the slice operation. This makes the slice operation fast.

string[] foodIdeasArray = new string[5] { "Spaghetti Bolognese", "Chicken Tikka Masala", "Chili Con Carne", "Beef Stroganoff", "Shepherd's Pie" };//Declare a string array
int[] intArray = new int[10] { 32, 5, 893, 898, 32, 43, 5, 59, 28, 325 };//Declare an int array
double[] doubleArray = new double[10] { 5, 13.5, 23.5, 1.5, 2.5, 494.9, 8291, 288, 2.42, 11 };//Declare a double array
float[] floatArray = new float[10] { 612f, 895f, 3f, 784f, 56f, 127f, 2f, 108f, 88f, 898f };//Declare a float array
bool[] boolArray = new bool[5] { false, false, true, true, false };//Declare a bool array

SpliceArray<string>(foodIdeasArray, 1, 3);//Splice array
SpliceArray<int>(intArray, 1, 3);//Splice array
SpliceArray<double>(doubleArray, 1, 3);//Splice array
SpliceArray<float>(floatArray, 1, 3);//Splice array
SpliceArray<bool>(boolArray, 1, 3);//Splice array

void SpliceArray<T>(T[] array, int startIndex, int endIndex)
{
    Console.WriteLine($"Starting array:");
    PrintArray<T>(array);
    Console.WriteLine();
    int count = endIndex - startIndex + 1;
    ArraySegment<T> arraySegment = new ArraySegment<T>(array, startIndex, count);//ArraySegment sets the start and end places to slice the array.
    //ArraySegment<T> arraySegment = new ArraySegment<T>(array);//Alternate way to slice with the same arraySegment
    //arraySegment.Slice(array, startIndex, count);//Slices the arraySegment
    Console.WriteLine("Sliced array:");
    PrintSlicedArray<T>(arraySegment);
    Console.WriteLine();
    Console.WriteLine();
}

void PrintSlicedArray<T>(ArraySegment<T> arraySegment)
{
    int index = arraySegment.Offset;
    foreach (T item in arraySegment)
    {
        Console.WriteLine($"[{index++}] {item}");
    }
}

void PrintArray<T>(T[] array)
{
    int index = 0;
    foreach (T item in array)
    {
        Console.WriteLine($"[{index++}] = {item}");
    }
}
Code Output
Starting array:
[0] = Spaghetti Bolognese
[1] = Chicken Tikka Masala
[2] = Chili Con Carne
[3] = Beef Stroganoff
[4] = Shepherd's Pie

Sliced array:
[1] Chicken Tikka Masala
[2] Chili Con Carne
[3] Beef Stroganoff


Starting array:
[0] = 32
[1] = 5
[2] = 893
[3] = 898
[4] = 32
[5] = 43
[6] = 5
[7] = 59
[8] = 28
[9] = 325

Sliced array:
[1] 5
[2] 893
[3] 898


Starting array:
[0] = 5
[1] = 13.5
[2] = 23.5
[3] = 1.5
[4] = 2.5
[5] = 494.9
[6] = 8291
[7] = 288
[8] = 2.42
[9] = 11

Sliced array:
[1] 13.5
[2] 23.5
[3] 1.5


Starting array:
[0] = 612
[1] = 895
[2] = 3
[3] = 784
[4] = 56
[5] = 127
[6] = 2
[7] = 108
[8] = 88
[9] = 898

Sliced array:
[1] 895
[2] 3
[3] 784


Starting array:
[0] = False
[1] = False
[2] = True
[3] = True
[4] = False

Sliced array:
[1] False
[2] True
[3] True

Span Method For Slicing Arrays

Span allocates memory to the stack rather than the managed heap. This is can have an advantage in speed in some cases. It does make a copy of the data so can keep that in mind if you need to refer to the array or not. This was also release in 2018 so you'll need to check the version that you working to see if it's there.

Span Slice Method Example Code

This is similar to the ArraySegment and you can pass the parameters to the constructor or call a Slice method.

string[] foodIdeasArray = new string[5] { "Tacos", "Pad Thai", "Carbonara", "Butter Chicken", "Lasagna" };//Declare a string array
int[] intArray = new int[10] { 312, -53, 2843, 98, 80, 4303, 5909, 9, 28, 325 };//Declare an int array
double[] doubleArray = new double[10] { -50, 23.5, 033.5, -13.5, 2.5, 494.99, -82991, 289, -2.42, 11 };//Declare a double array
float[] floatArray = new float[10] { 610f, 85f, 3f, 780f, -54806f, 17f, 283f, 198f, 8884f, 8958f };//Declare a float array
bool[] boolArray = new bool[5] { true, false, false, true, true };//Declare a bool array

SpliceArray<string>(foodIdeasArray, 2, 4);//Splice array
SpliceArray<int>(intArray, 2, 4);//Splice array
SpliceArray<double>(doubleArray, 2, 4);//Splice array
SpliceArray<float>(floatArray, 2, 4);//Splice array
SpliceArray<bool>(boolArray, 2, 4);//Splice array

void SpliceArray<T>(T[] array, int startIndex, int endIndex)
{
    Console.WriteLine($"Starting array:");
    PrintArray<T>(array);
    Console.WriteLine();
    int count = endIndex - startIndex + 1;
    Span<T> span = new Span<T>(array, startIndex, count);//Span sets the start and end places to slice the array.
    //Span<T> span = new Span<T>(array);//Pass whole array into the span
    //span.Slice(startIndex, count);//Slice sets the start and end places to slice the array.
    Console.WriteLine("Sliced array:");
    PrintSlicedArray<T>(span, startIndex);
    Console.WriteLine();
    Console.WriteLine();
}

void PrintSlicedArray<T>(Span<T> slicedArray, int startIndex)
{
    int index = startIndex;
    foreach (T item in slicedArray)
    {
        Console.WriteLine($"[{index++}] {item}");
    }
}
Code Output
Starting array:
[0] = Tacos
[1] = Pad Thai
[2] = Carbonara
[3] = Butter Chicken
[4] = Lasagna

Sliced array:
[2] Carbonara
[3] Butter Chicken
[4] Lasagna


Starting array:
[0] = 312
[1] = -53
[2] = 2843
[3] = 98
[4] = 80
[5] = 4303
[6] = 5909
[7] = 9
[8] = 28
[9] = 325

Sliced array:
[2] 2843
[3] 98
[4] 80


Starting array:
[0] = -50
[1] = 23.5
[2] = 33.5
[3] = -13.5
[4] = 2.5
[5] = 494.99
[6] = -82991
[7] = 289
[8] = -2.42
[9] = 11

Sliced array:
[2] 33.5
[3] -13.5
[4] 2.5


Starting array:
[0] = 610
[1] = 85
[2] = 3
[3] = 780
[4] = -54806
[5] = 17
[6] = 283
[7] = 198
[8] = 8884
[9] = 8958

Sliced array:
[2] 3
[3] 780
[4] -54806


Starting array:
[0] = True
[1] = False
[2] = False
[3] = True
[4] = True

Sliced array:
[2] False
[3] True
[4] True

Range Operator Method For Slicing Arrays

The range operator is a completely different syntax for dealing with indexes and ranges of an array. So if you have a range of slicing requirements then the range operator should be able to accommodate you. It uses the range system which is a new syntax.

Range Operator Method Example Code

Using the range system, I provided a start and end index. I had to add 1 to the end index to get the desired number of entries consistent with the previous example.

string[] foodIdeasArray = new string[5] { "Tacos", "Pad Thai", "Carbonara", "Butter Chicken", "Lasagna" };//Declare a string array
int[] intArray = new int[10] { 312, -53, 2843, 98, 80, 4303, 5909, 9, 28, 325 };//Declare an int array
double[] doubleArray = new double[10] { -50, 23.5, 033.5, -13.5, 2.5, 494.99, -82991, 289, -2.42, 11 };//Declare a double array
float[] floatArray = new float[10] { 610f, 85f, 3f, 780f, -54806f, 17f, 283f, 198f, 8884f, 8958f };//Declare a float array
bool[] boolArray = new bool[5] { true, false, false, true, true };//Declare a bool array

SpliceArray<string>(foodIdeasArray, 2, 4);//Splice array
SpliceArray<int>(intArray, 2, 4);//Splice array
SpliceArray<double>(doubleArray, 2, 4);//Splice array
SpliceArray<float>(floatArray, 2, 4);//Splice array
SpliceArray<bool>(boolArray, 2, 4);//Splice array

void SpliceArray<T>(T[] array, int startIndex, int endIndex)
{
    Console.WriteLine($"Starting array:");
    PrintArray<T>(array);
    Console.WriteLine();
    T[] slice = array[startIndex..(endIndex + 1)];
    Console.WriteLine("Sliced array:");
    PrintArray<T>(slice, startIndex);
    Console.WriteLine();
    Console.WriteLine();
}

void PrintArray<T>(T[] array, int startIndex = 0)
{
    int index = startIndex;
    foreach (T item in array)
    {
        Console.WriteLine($"[{index++}] = {item}");
    }
}
Code Output
[2] = 2843
[3] = 98
[4] = 80
[5] = 4303
[6] = 5909
[7] = 9
[8] = 28
[9] = 325

Sliced array:
[2] = 2843
[3] = 98
[4] = 80


Starting array:
[0] = -50
[1] = 23.5
[2] = 33.5
[3] = -13.5
[4] = 2.5
[5] = 494.99
[6] = -82991
[7] = 289
[8] = -2.42
[9] = 11

Sliced array:
[2] = 33.5
[3] = -13.5
[4] = 2.5


Starting array:
[0] = 610
[1] = 85
[2] = 3
[3] = 780
[4] = -54806
[5] = 17
[6] = 283
[7] = 198
[8] = 8884
[9] = 8958

Sliced array:
[2] = 3
[3] = 780
[4] = -54806


Starting array:
[0] = True
[1] = False
[2] = False
[3] = True
[4] = True

Sliced array:
[2] = False
[3] = True
[4] = True

LINQ Skip Take Method For Slicing Arrays

Skip enables it to jump to a starting point in the array while take allows for a stopping point in the array.

LINQ Skip Take Method Example Code

In this example, I want to skip to the second index and take up to the 4th index so I need to add one to the take parameter.


string[] foodIdeasArray = new string[5] { "Tacos", "Pad Thai", "Carbonara", "Butter Chicken", "Lasagna" };//Declare a string array
int[] intArray = new int[10] { 312, -53, 2843, 98, 80, 4303, 5909, 9, 28, 325 };//Declare an int array
double[] doubleArray = new double[10] { -50, 23.5, 033.5, -13.5, 2.5, 494.99, -82991, 289, -2.42, 11 };//Declare a double array
float[] floatArray = new float[10] { 610f, 85f, 3f, 780f, -54806f, 17f, 283f, 198f, 8884f, 8958f };//Declare a float array
bool[] boolArray = new bool[5] { true, false, false, true, true };//Declare a bool array

SpliceArray<string>(foodIdeasArray, 2, 4);//Splice array
SpliceArray<int>(intArray, 2, 4);//Splice array
SpliceArray<double>(doubleArray, 2, 4);//Splice array
SpliceArray<float>(floatArray, 2, 4);//Splice array
SpliceArray<bool>(boolArray, 2, 4);//Splice array

void SpliceArray<T>(T[] array, int startIndex, int endIndex)
{
    Console.WriteLine($"Starting array:");
    PrintArray<T>(array);
    Console.WriteLine();
    IEnumerable<T> slice = array.Skip(startIndex).Take(endIndex -1);//LINQ skip to the start index and take from end index
    Console.WriteLine("Sliced array:");
    PrintArray<T>(slice, startIndex);
    Console.WriteLine();
    Console.WriteLine();
}

void PrintArray<T>(IEnumerable<T> array, int startIndex = 0)
{
    int index = startIndex;
    foreach (T item in array)
    {
        Console.WriteLine($"[{index++}] = {item}");
    }
}
Code Output
Starting array:
[0] = Tacos
[1] = Pad Thai
[2] = Carbonara
[3] = Butter Chicken
[4] = Lasagna

Sliced array:
[2] = Carbonara
[3] = Butter Chicken
[4] = Lasagna


Starting array:
[0] = 312
[1] = -53
[2] = 2843
[3] = 98
[4] = 80
[5] = 4303
[6] = 5909
[7] = 9
[8] = 28
[9] = 325

Sliced array:
[2] = 2843
[3] = 98
[4] = 80


Starting array:
[0] = -50
[1] = 23.5
[2] = 33.5
[3] = -13.5
[4] = 2.5
[5] = 494.99
[6] = -82991
[7] = 289
[8] = -2.42
[9] = 11

Sliced array:
[2] = 33.5
[3] = -13.5
[4] = 2.5


Starting array:
[0] = 610
[1] = 85
[2] = 3
[3] = 780
[4] = -54806
[5] = 17
[6] = 283
[7] = 198
[8] = 8884
[9] = 8958

Sliced array:
[2] = 3
[3] = 780
[4] = -54806


Starting array:
[0] = True
[1] = False
[2] = False
[3] = True
[4] = True

Sliced array:
[2] = False
[3] = True
[4] = True

Performance Test

This is a test to see the average speed over 10 tests. Each test has 100 function calls to the test method and each test method has 5 million objects. This will be the same test against all the methods to see how each compares.

ArraySegment Speed Test Code

int startIndex = 10;
int endIndex = 40;
int count = endIndex - startIndex + 1;

void TestMethod(string[] array)
{
    ArraySegment<string> arraySegment = new ArraySegment<string>(array, startIndex, count);//ArraySegment sets the start and end places to slice the array.
    foreach (string item in arraySegment)
    {
        //some logic
    }
}

Span Test Code

int startIndex = 10;
int endIndex = 40;
int count = endIndex - startIndex + 1;

void TestMethod(string[] array)
{
    Span<string> span = new Span<string>(array, startIndex, count);//Span sets the start and end places to slice the array.
    foreach (string item in span)
    {
        //some logic
    }
}

Range Operator Test Code

int startIndex = 10;
int endIndex = 40;
int count = endIndex - startIndex + 1;

void TestMethod(string[] array)
{
    Span<string> span = new Span<string>(array, startIndex, count);//Span sets the start and end places to slice the array.
    foreach (string item in span)
    {
        //some logic
    }
}

LINQ Skip Take Operator Test Code

void TestMethod(string[] array)
{
    IEnumerable<string> slice = array.Skip(startIndex).Take(endIndex -1);//LINQ skip to the start index and take from end index
    foreach (string item in slice)
    {
        //some logic
    }
}
ArraySegment Code Output
Test 1:Function Calls:100, In 0m 0s 0ms 500ns
Test 2:Function Calls:100, In 0m 0s 0ms 400ns
Test 3:Function Calls:100, In 0m 0s 0ms 200ns
Test 4:Function Calls:100, In 0m 0s 0ms 100ns
Test 5:Function Calls:100, In 0m 0s 0ms 700ns
Test 6:Function Calls:100, In 0m 0s 0ms 0ns
Test 7:Function Calls:100, In 0m 0s 0ms 200ns
Test 8:Function Calls:100, In 0m 0s 0ms 0ns
Test 9:Function Calls:100, In 0m 0s 0ms 600ns
Test 10:Function Calls:100, In 0m 0s 0ms 700ns
ArraySegment Average Speed:340ns, In 10 Tests
Span Code Output
Test 1:Function Calls:100, In 0m 0s 0ms 300ns
Test 2:Function Calls:100, In 0m 0s 0ms 200ns
Test 3:Function Calls:100, In 0m 0s 0ms 500ns
Test 4:Function Calls:100, In 0m 0s 0ms 500ns
Test 5:Function Calls:100, In 0m 0s 0ms 400ns
Test 6:Function Calls:100, In 0m 0s 0ms 600ns
Test 7:Function Calls:100, In 0m 0s 0ms 200ns
Test 8:Function Calls:100, In 0m 0s 0ms 0ns
Test 9:Function Calls:100, In 0m 0s 0ms 700ns
Test 10:Function Calls:100, In 0m 0s 0ms 300ns
Span Average Speed:370ns, In 10 Tests
Range Operator Code Output
Test 1:Function Calls:100, In 0m 0s 0ms 0ns
Test 2:Function Calls:100, In 0m 0s 0ms 700ns
Test 3:Function Calls:100, In 0m 0s 0ms 0ns
Test 4:Function Calls:100, In 0m 0s 0ms 500ns
Test 5:Function Calls:100, In 0m 0s 0ms 100ns
Test 6:Function Calls:100, In 0m 0s 0ms 900ns
Test 7:Function Calls:100, In 0m 0s 0ms 800ns
Test 8:Function Calls:100, In 0m 0s 0ms 900ns
Test 9:Function Calls:100, In 0m 0s 0ms 800ns
Test 10:Function Calls:100, In 0m 0s 0ms 800ns
Range Operator Average Speed:550ns, In 10 Tests
LINQ Skip Take Code Output
Test 1:Function Calls:100, In 0m 0s 0ms 500ns
Test 2:Function Calls:100, In 0m 0s 0ms 100ns
Test 3:Function Calls:100, In 0m 0s 0ms 800ns
Test 4:Function Calls:100, In 0m 0s 0ms 800ns
Test 5:Function Calls:100, In 0m 0s 0ms 0ns
Test 6:Function Calls:100, In 0m 0s 0ms 0ns
Test 7:Function Calls:100, In 0m 0s 0ms 700ns
Test 8:Function Calls:100, In 0m 0s 0ms 300ns
Test 9:Function Calls:100, In 0m 0s 0ms 100ns
Test 10:Function Calls:100, In 0m 0s 0ms 500ns
LINQ Skip Take Average Speed:380ns, In 10 Tests
Full ArraySegment Speed Test Code

using System.Diagnostics;

int numberOfTests = 10;//Number of tests 
int numberOfFunctionCalls = 100;//Number of function calls made per test
int numberOfObjectsToCreate = 5000000;//Number test objects
int lengthOfRandomString = 50;
string testName = "ArraySegment";//Test name to print to average
int startIndex = 10;
int endIndex = 40;
int count = endIndex - startIndex + 1;
void TestMethod(string[] array)
{
    ArraySegment<string> arraySegment = new ArraySegment<string>(array, startIndex, count);//ArraySegment sets the start and end places to slice the array.
    foreach (string item in arraySegment)
    {
        //some logic
    }
}

List<double> testSpeedList = new List<double>();
for (int testIndex = 0; testIndex < numberOfTests; testIndex++)
{
    testSpeedList.Add(StartTest(testIndex));
}
Console.WriteLine($"{testName} Average Speed:{Math.Round(testSpeedList.Average())}ns, In {numberOfTests} Tests");

double StartTest(int testIndex)
{
    Stopwatch stopwatch = new Stopwatch();
    string[] testData = GetArrayData();//Get intial random generated data
    for (int i = 0; i < numberOfFunctionCalls; i++)
    {
        stopwatch.Start();//Start the Stopwatch timer
        TestMethod(testData);//
        stopwatch.Stop();//Stop the Stopwatch timer
    }
    stopwatch.Stop();//Stop the Stopwatch timer
    Console.WriteLine($"Test {testIndex + 1}:Function Calls:{numberOfFunctionCalls}, In {stopwatch.Elapsed.Minutes}m {stopwatch.Elapsed.Seconds}s {stopwatch.Elapsed.Milliseconds}ms {stopwatch.Elapsed.Nanoseconds}ns");
    return stopwatch.Elapsed.Nanoseconds;
}

string[] GetArrayData()
{
    string[] testData = new string[numberOfObjectsToCreate];
    for (int i = 0; i < numberOfObjectsToCreate; i++)
    {
        string key = GenerateRandomString(lengthOfRandomString);

        string value = GenerateRandomString(lengthOfRandomString);
        testData[i] = value;

    }
    return testData;
}

string GenerateRandomString(int length)
{
    Random random = new Random();
    const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    return new string(Enumerable.Repeat(chars, length)
      .Select(s => s[random.Next(s.Length)]).ToArray());
}

Conclusion

Overall RankMethodSpeed
1ArraySegment340ns
2Span370ns
3Range Operator550ns
4LINQ Skip Take380ns

The best method for slicing an array is ArraySegment. It is a wrapper for the array and does not copy it but it is the fastest method of these and has long support through a lot of versions of .NET.

If you need something more flexible then take a look at the range operator. It has a lot of options for how to splice up the array.

LINQ Skip Take is good if you like LINQ but the syntax makes it less readable.

All of these operations are extremely fast and even under heavy testing they all did less than 1 ms which is amazing.

Know any other ways to slice an array? Let me know in the comments below.

Get Latest Updates