TheDeveloperBlog.com


C# Buffer Class: BlockCopy, ByteLength and GetByte

Buffer. The Buffer type handles ranges of bytes. It includes the optimized Buffer.BlockCopy method—this copies a range of bytes from one array to another. It provides too the ByteLength, GetByte and SetByte methods.


BlockCopy. To begin, Buffer.BlockCopy does not copy logical elements in arrays. Instead it copies bytes. We call it to copy the bytes in one byte array to another. This is useful for compression, images, or many other types of binary data.

Byte
Based on:

.NET 4.5

C# program that uses BlockCopy on bytes

using System;

class Program
{
    static void Main()
    {
	byte[] arr1 = new byte[] { 1, 2, 3, 4, 5 };
	byte[] arr2 = new byte[10];

	// Copy the first five bytes from arr1 to arr2
	Buffer.BlockCopy(arr1, 0, arr2, 0, 5);

	Display(arr2);
    }

    static void Display(byte[] arr)
    {
	for (int i = 0; i < arr.Length; i++)
	{
	    Console.Write(arr[i]);
	}
	Console.WriteLine();
    }
}

Output

1234500000

In this example, we get the expected result after calling Buffer.BlockCopy. The ints are actually used as bytes, not 4-byte ints. The byte array arr2 is automatically initialized to all zero bytes.

Byte Array

Example 2. Buffer.BlockCopy is different when using a data type that is not 1 byte. An int is 4 bytes. The fifth parameter of Buffer.BlockCopy is the number of bytes to copy, not array elements. We must multiply it by 4, or the sizeof(int).

In this example, the sizeof() operator returns the byte length of a primitive data type like int. Here it returns 4. The multiplication of sizeof(int) * 4 can be resolved by the C# compiler—it won't cause any slowdowns.

C# program that copies ints

using System;

class Program
{
    static void Main()
    {
	int[] arr1 = new int[] { 1, 2, 3, 4, 5 };
	int[] arr2 = new int[10];

	// Copy the first twenty bytes from arr1 to arr2
	Buffer.BlockCopy(arr1, 0, arr2, 0, 5 * sizeof(int));

	Display(arr2);
    }

    static void Display(int[] arr)
    {
	for (int i = 0; i < arr.Length; i++)
	{
	    Console.Write(arr[i]);
	}
	Console.WriteLine();
    }
}

Output

1234500000


Benchmark. To test Buffer.BlockCopy further, I benchmarked it on a 1000-element array. I found it has a significant performance advantage over Array.Copy. And copying the elements in a for-loop was far slower.

Array.CopyFor
Method that returns byte arrays: C#

static byte[] GetSourceArray()
{
    // [Note] You can populate data here
    return new byte[_len];
}

Code that copies byte arrays: C#
    Iterations tested: 10000000

const int _len = 1000;

Buffer.BlockCopy(source, 0, target, 0, _len);

Array.Copy(source, target, _len);

The benchmark tested an array of 1000 random byte values, but these did not affect behavior in any way, as expected. The code here doesn't include that part. We demonstrated the performance improvement when using Buffer.BlockCopy.

Benchmark results

Buffer.BlockCopy: 1113 ms [faster]
Array.Copy:       1343 ms


ByteLength returns the byte count for an array. In this example there are two arrays—an integer array and a byte array—allocated on the managed heap. We use Buffer.ByteLength to count the total number of bytes in each array.

Note: Each int is four bytes. Each byte is a single byte. The lengths returned reflect this.

C# program that uses Buffer.ByteLength method

using System;

class Program
{
    static void Main()
    {
	// Example arrays for program.
	// ... Each array has three elements.
	int[] array1 = { 1, 2, 3 };
	byte[] array2 = { 1, 2, 3 };

	// Get lengths of arrays.
	// ... This counts the bytes in all elements.
	int length1 = Buffer.ByteLength(array1);
	int length2 = Buffer.ByteLength(array2);

	// Write results.
	Console.WriteLine(length1);
	Console.WriteLine(length2);
    }
}

Output

12
3

Internally, the Buffer.ByteLength method accesses external code in the .NET Framework. The Buffer class uses lower-level methods such as memcpy. This can provide performance advantages.

Note: The Buffer.ByteLength method likely uses some lower-level functions in native code to compute the byte length.

In earlier releases of the .NET Framework, the sizeof operator was not available except in unsafe code contexts. For these .NET programs, Buffer.ByteLength could provide a way for element sizes to be computed without using sizeof.

However: In current .NET releases, the sizeof operator is available in all contexts for value types. It evaluates to a constant.

Tip: ByteLength is useful when you want to encode an array of value types as an array of bytes directly.

Also: Please see the BitConverter class for information on how you can change representations of value types.

BitConverter

GetByte, SetByte. Next, we use the Buffer.GetByte and Buffer.SetByte methods. We have an array of three integer values (an int array). Note that integers (int variables) in C# code are represented by four bytes.

Int ArrayInt

By using GetByte and SetByte, we read and assign the individual bytes in each integer directly. This changes the decimal representation of the integer by changing the bytes in the integer.

And: This is used to treat an integer array as a bitmask or other structure. One bit is used to indicate true or false.

True, False
C# program that uses Buffer.GetByte and Buffer.SetByte

using System;

class Program
{
    static void Main()
    {
	// Use an array of three integers for testing.
	// ... Loop through the bytes in the array and write their values.
	int[] array1 = { 1, 1, 256 };
	for (int i = 0; i < Buffer.ByteLength(array1); i++)
	{
	    Console.WriteLine(Buffer.GetByte(array1, i));
	}

	// Set certain byte values at indexes in the array.
	Buffer.SetByte(array1, 0, 55);
	Buffer.SetByte(array1, 4, 55);
	Buffer.SetByte(array1, 8, 55);

	// Render the modified array.
	Console.WriteLine("---");
	for (int i = 0; i < Buffer.ByteLength(array1); i++)
	{
	    Console.WriteLine(Buffer.GetByte(array1, i));
	}
    }
}

Output

1
0
0
0
1
0
0
0
0
1
0
0
---
55
0
0
0
55
0
0
0
55
1
0
0

Treating arrays as blocks of memory. In the .NET Framework, arrays are stored in a single memory region and this is on the managed heap. The Buffer methods here, GetByte and SetByte, act upon this region.

Tip: You can read and manipulate data in an array memory region just by indexes.

These methods are not written in C# code in the .NET Framework. They are implemented in C or C++, and this can provide more performance for cases where large arrays or frequent operations upon them are necessary.


Summary. The Buffer type contains low-level methods that act upon bytes. A buffer is a range of bytes. With BlockCopy, we optimize large array copies of value types. With ByteLength, we compute the total number of bytes in a value type array.

Optimizations