In this article, we will learn how to merge arrays in C#. This functionality will allow us to combine two or more arrays. Working with arrays is essential when performance matters to us since arrays work faster than many other data structures. We will use different ways to merge arrays in C#. Based on the different methods, we will compare the efficiency of each approach.

To download the source code for this article, you can visit our GitHub repository.

Let’s move on.

Prepare the Environment

First, let’s add the MergeArrayBenchmark class, which we will use to compare different ways of merging arrays.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

To start with, let’s create a private field to set the default length of an array: 

public class MergeArrayBenchmark
{
    private static readonly int _arraySize = 1000;
}

Here, we create the _arraySize variable with the initial value set to one thousand.

Then, let’s add a private static method for creating a source array:

public IEnumerable<object[]> GetSourceArrayPopulatedWithNumbers()
{
    var first = Enumerable.Repeat(1, _arraySize).ToArray();
    var second = Enumerable.Repeat(2, _arraySize).ToArray();

    yield return new object[] { first, second };
}

The static Repeat method is used from the Enumerable class to generate a sequence with one repeated value for a specific number of times. In this case, we create a specific number of records with int values.

Lastly, let’s introduce the BenchmarkDotNet library to get the benchmark results. We are going to add a MemoryDiagnoser annotation to the MergeArrayBenchmark class to get memory allocation results for each merging method:

[MemoryDiagnoser, Orderer(SummaryOrderPolicy.FastestToSlowest)]
public class MergeArrayBenchmark

We also use the Orderer to order the results from fastest to slowest.

Using Array.Copy to Merge Arrays in C#

The first approach is the static Copy method from the Array class. This method copies a range of elements in one array to another array. Additionally, it can perform casting and boxing as required. With the Array.Copy method, we can define the source array, the initial index of the source array, the destination array, and the number of elements we want to copy. While copying from one array to another, we have to use the same data type in both arrays.

To examine all the ways, we are going to try two different methods of merging arrays using the Copy method. 

Array.Copy with New Array

In the first case, let’s use the Copy method and copy the data from both arrays to the new destination array:

[Benchmark]
[ArgumentsSource(nameof(GetSourceArrayPopulatedWithNumbers))]
public int[] MergeUsingArrayCopyWithNewArray(int[] firstArray, int[] secondArray)
{
    var combinedArray = new int[firstArray.Length + secondArray.Length];
    Array.Copy(firstArray, combinedArray, firstArray.Length);
    Array.Copy(secondArray, 0, combinedArray, firstArray.Length, secondArray.Length);

    return combinedArray;
}

We use the ArgumentsSource attribute to populate the firstArray and the secondArray with generated values.

Then, we create a new combinedArray variable using the total size of both initial arrays. Next, we use the Array.Copy method to copy all of the data from the firstArray to the combinedArray variable as the destination array. Additionally, we pass the length of the source array to copy the data.

After that, we use the same method to copy the data from the secondArray to the combinedArray. The only difference is that we specify the source and destination index. The source index is zero, and the destination index is firstArray.Length to add the data after the last element in the array.

Note that we add the Benchmark annotation from the BenchmarkDotNet library to measure the performance when the method is run.

Array.Copy with Array.Resize

Secondly, let’s try another way of using the Copy method to change the size of an already existing array:

[Benchmark]
[ArgumentsSource(nameof(GetSourceArrayPopulatedWithNumbers))]
public int[] MergeUsingArrayCopyWithResize(int[] firstArray, int[] secondArray)
{
    var originalFirstArrayLength = firstArray.Length;
    int[] firstTempArray = Array.Empty<int>();

    Array.Resize(ref firstTempArray, firstArray.Length + secondArray.Length);
    Array.Copy(secondArray, 0, firstTempArray, originalFirstArrayLength, secondArray.Length);

    return firstTempArray;
}

In the first step, we store the original length of the firstArray into the originalFirstArrayLength variable. Next, we resize the firstTempArray passing its reference and the total length of both arrays (first and second array) to the Resize static method from the Array class. After that, we use the Copy method to specify the source array, the source index, the destination array, and the original lengths of both source and destination arrays.

Merging Arrays Using CopyTo

This approach is similar to the method from the previous section. Here we don’t have to use the static class to perform the merge operation. That said, the CopyTo being an instance method is the only difference between the two.

Let’s see how we can use the CopyTo method to merge arrays:

[Benchmark]
[ArgumentsSource(nameof(GetSourceArrayPopulatedWithNumbers))]
public int[] MergeUsingArrayCopyTo(int[] firstArray, int[] secondArray)
{
    var combinedArray = new int[firstArray.Length + secondArray.Length];
    firstArray.CopyTo(combinedArray, 0);
    secondArray.CopyTo(combinedArray, firstArray.Length);

    return combinedArray;
}

Again, we create the combinedArray variable as the destination array. Next, we use the CopyTo method directly from our firstArray object to copy the data into the combinedArray variable with the index set to zero.

Finally, we perform the same functionality on the secondArray. The only difference is that we set the index to the size of the firstArray. With that, we add the secondArray records after the existing data.

Merge Arrays in C# Using LINQ Methods

In this section, we will use different LINQ methods to merge arrays.

We can use all the LINQ methods that we mention in this article from the static Enumerable class. However, we will use extension methods because they are arguably more readable and are not limited to two sequences.

For example, we can use the static Concat method:

Enumerable.Concat(firstArray, secondArray).ToArray();

And the extension Concat method:

firstArray.Concat(secondArray).ToArray();

Merging Arrays with LINQ’s Concat

The first LINQ method is the Concat method. We can use this method to append two sequences or collections to return a new sequence or collection. In our case, we will use it to concatenate two arrays. The Concat method allows the usage of duplicate elements in arrays and returns the items from the first sequence followed by the elements from the second sequence.

Like other LINQ methods in this article, this method is implemented by using deferred execution. Deferred execution in LINQ means that we can delay the evaluation of an expression until we require the actual value. We call this concept lazy loading. With that, we can improve the performance of our app.

Let’s see the actual implementation of the Concat method when merging two arrays:

[Benchmark]
[ArgumentsSource(nameof(GetSourceArrayPopulatedWithNumbers))]
public int[] MergeUsingLinqConcat(int[] firstArray, int[] secondArray)
{
    var combinedArray = firstArray.Concat(secondArray).ToArray();

    return combinedArray;
}

In this scenario, we create the combinedArray variable by simply using the Concat method, where we append the secondArray to the firstArray. After that, we return the concatenated array because the Concat method returns IEnumerable by default.

Using LINQ’s Union to Merge Arrays

Next up is the Union method from LINQ. We can use the Union method to combine the multiple data sources into one. It is important to note that this method will remove all duplicate elements from the data sources. Based on that, it will compare references of array elements.

Union is basically Concat followed by Distinct, so we can expect the Union method to take more time to execute.

Now, let’s combine both arrays by eliminating the duplicate elements:

[Benchmark]
[ArgumentsSource(nameof(GetSourceArrayPopulatedWithNumbers))]
public int[] MergeUsingLinqUnion(int[] firstArray, int[] secondArray)
{
    var combinedArray = firstArray.Union(secondArray).ToArray();

    return combinedArray;
}

Similar to the previous example, we initialize the new array and assign it to the combinedArray variable with the Union method used on the firstArray to append the data from the secondArray and eliminate the duplicates.

Merging Arrays With LINQ SelectMany

The last method from LINQ is the SelectMany method. This method projects each element of a sequence to an IEnumerable and flattens the resulting sequences into one sequence. Based on that, the SelectMany method combines the records from all arrays and converts them into one array.

We need to do a little more work when using the SelectMany method:

[Benchmark]
[ArgumentsSource(nameof(GetSourceArrayPopulatedWithNumbers))]
public int[] MergeUsingLinqSelectMany(int[] firstArray, int[] secondArray)
{
    var firstAndSecondArray = new[] { firstArray, secondArray };
    var combinedArray = firstAndSecondArray.SelectMany(animal => animal).ToArray();

    return combinedArray;
}

First, we create the firstAndSecondArray variable as a multidimensional array. Then, we call the SelectMany method from the firstAndSecondArray and select the objects before we convert the collection into the combinedArray variable.

Merging Arrays With Buffer.BlockCopy

In this example, we are going to use the static BlockCopy method from the Buffer class. This method copies a specified number of bytes from a source array to a destination array. In addition to that, we can set a particular offset for both source and destination arrays.

When we compare the Buffer.BlockCopy method to the Array.Copy, there is a minor difference in syntax. However, the BlockCopy method should be faster when merging arrays with primitive types:

[Benchmark]
[ArgumentsSource(nameof(GetSourceArrayPopulatedWithNumbers))]
public int[] MergeUsingBlockCopy(int[] firstArray, int[] secondArray)
{
    var combinedArray = new int[firstArray.Length + secondArray.Length];

    Buffer.BlockCopy(firstArray, 0, combinedArray, 0, firstArray.Length);
    Buffer.BlockCopy(secondArray, 0, combinedArray, firstArray.Length, secondArray.Length);

    return combinedArray;
}

As in the previous examples, we use the firstArray and the secondArray variables, containing the objects of type int.

However, we cannot use the BlockCopy method with the reference types in C#, only with the primitives. To avoid this, we can change classes to structures, and after that, we would be able to use pointers with the BlockCopy method.

The BlockCopy method accepts five different parameters in this case:

  • Source array
  • Source offset
  • Destination array
  • Destination offset
  • Number of records

Merge Arrays in C# Manually

We have the option to implement the merging of arrays manually. In this case, we will spend more time on implementation, and the source code will be longer. On the other hand, sometimes manually implemented functionalities can work faster since they are often adapted to our use case.

That said, let’s implement a manual way of merging arrays by adding a new combined array:

[Benchmark]
[ArgumentsSource(nameof(GetSourceArrayPopulatedWithNumbers))]
public int[] MergeUsingNewArrayManually(int[] firstArray, int[] secondArray)
{
    var combinedArray = new int[firstArray.Length + secondArray.Length];
    for (int i = 0; i < combinedArray.Length; i++)
    {
        if (i >= firstArray.Length)
        {
            combinedArray[i] = secondArray[i - firstArray.Length];
        }
        else
        {
            combinedArray[i] = firstArray[i];
        }
    }

    return combinedArray;
}

In the MergeUsingNewArrayManually method, we iterate through initial arrays and add elements to the destination array using the index. First, we determine the total length of the destination array and create the combinedArray variable. Then, we use a for loop and retrieve specific elements using the index. Therefore, we assign the retrieved element according to the index in the destination array.

Merge Arrays in C# Benchmark Comparison

Finally, we are going to implement the benchmark Run method which we can execute from the Program class:

BenchmarkRunner.Run<MergeArrayBenchmark>();

Benchmark Comparison with 1000 Records

Now, we can check the benchmark results with the 1000 records in each array.

So, let’s run the project in the release configuration with the dotnet run -c Release command, and get the results:

|                          Method |  firstArray | secondArray |        Mean |     Error |    StdDev | Allocated |
|-------------------------------- |------------ |------------ |------------:|----------:|----------:|----------:|
|             MergeUsingBlockCopy | Int32[1000] | Int32[1000] |    401.8 ns |   7.17 ns |   7.04 ns |   8,024 B |
|   MergeUsingArrayCopyWithResize | Int32[1000] | Int32[1000] |    407.0 ns |   8.13 ns |   7.98 ns |   8,024 B |
| MergeUsingArrayCopyWithNewArray | Int32[1000] | Int32[1000] |    525.9 ns |  10.25 ns |  10.97 ns |   8,024 B |
|           MergeUsingArrayCopyTo | Int32[1000] | Int32[1000] |    526.6 ns |  10.58 ns |  24.10 ns |   8,024 B |
|            MergeUsingLinqConcat | Int32[1000] | Int32[1000] |    646.3 ns |  10.24 ns |   9.58 ns |   8,136 B |
|        MergeUsingLinqSelectMany | Int32[1000] | Int32[1000] |    757.9 ns |  15.13 ns |  42.44 ns |   8,272 B |
|      MergeUsingNewArrayManually | Int32[1000] | Int32[1000] |  2,101.1 ns |  19.68 ns |  17.45 ns |   8,024 B |
|             MergeUsingLinqUnion | Int32[1000] | Int32[1000] | 17,486.4 ns | 342.18 ns | 634.25 ns |     336 B |

The fastest method for merging arrays of integers is the MergeUsingBlockCopy method. It is important to note that the Block.Copy method works only with primitive data types. With that, this is the expected result.

After that, we can see three different methods (MergeUsingArrayCopyWithResize, MergeUsingArrayCopyWithNewArray, MergeUsingArrayCopyTo) and their results may vary. Then we have two different LINQ approaches which are using the Concat and the SelectMany methods, with the Concat method being slightly faster.

At last, we have our manually implemented method with 2.101 nanoseconds and the MergeUsingLinqUnion method with 17.4 nanoseconds.

Memory allocation is similar or identical for all approaches, with the MergeUsingLinqUnion method as the most efficient.

Benchmark Comparison with 50,000 Records

We should check the performance of the methods with different sizes of arrays, so let’s populate the arrays with 50,000 records.

To do that, let’s simply modify our _arraySize variable in the MergeArrayBenchmark class:

private static readonly int _arraySize = 50000;

Now, let’s run the program again, and check the benchmark results:

|                          Method |   firstArray |  secondArray |      Mean |     Error |    StdDev |    Median | Allocated |
|-------------------------------- |------------- |------------- |----------:|----------:|----------:|----------:|----------:|
|             MergeUsingBlockCopy | Int32[50000] | Int32[50000] |  31.04 us |  0.616 us |  1.231 us |  30.77 us | 400,066 B |
|   MergeUsingArrayCopyWithResize | Int32[50000] | Int32[50000] |  36.31 us |  1.263 us |  3.665 us |  34.75 us | 400,066 B |
|           MergeUsingArrayCopyTo | Int32[50000] | Int32[50000] |  39.65 us |  0.670 us |  0.559 us |  39.76 us | 400,066 B |
|            MergeUsingLinqConcat | Int32[50000] | Int32[50000] |  40.81 us |  0.808 us |  1.478 us |  40.86 us | 400,178 B |
|        MergeUsingLinqSelectMany | Int32[50000] | Int32[50000] |  40.88 us |  0.739 us |  0.935 us |  41.12 us | 400,314 B |
| MergeUsingArrayCopyWithNewArray | Int32[50000] | Int32[50000] |  42.57 us |  0.847 us |  2.046 us |  42.33 us | 400,066 B |
|      MergeUsingNewArrayManually | Int32[50000] | Int32[50000] | 109.38 us |  2.102 us |  3.453 us | 108.66 us | 400,066 B |
|             MergeUsingLinqUnion | Int32[50000] | Int32[50000] | 819.17 us | 15.651 us | 18.631 us | 824.64 us |     336 B |

In the example with 50,000 records, we notice some differences. The static Copy method from the Block class is still in first place in terms of execution speed, with 31 microseconds.

Again, memory usage is similar in all examples, with the Union method being better since it doesn’t have to allocate memory for duplicates.

Conclusion

In this article, we’ve learned how to implement different ways to merge arrays in C#. We have seen that there are many different ways to merge arrays. With different methods, we got different results. In the case of merging primitive data types in C#, the Block.Copy method proved to be the fastest. On the other hand, with reference data types, we can use the Array.Copy method, or the CopyTo extension method if we don’t prefer static methods.

LINQ methods are also very efficient, except for the Union method, which is expectedly slower due to duplicate removal.

Finally, we can use manually implemented methods, which turned out to be slower, but everything depends on the use case.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!