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.
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.
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.