Updated on

Encoding.UTF8.GetBytes(message) converts a C# string to a byte array, and for almost every case that is the whole answer.

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

The interesting part is what the other three approaches do differently. Casting characters to bytes and calling Convert.ToByte() both work on a narrow range of characters and behave very differently once a string leaves it, and Encoding.GetEncoding() opens up named encodings at a cost the article did not previously mention.

Why Do We Convert a String to a Byte Array in C#?

A C# string is a sequence of UTF-16 code units, and a byte array is raw bytes with no meaning attached. An encoding is the rule that turns one into the other.

We need that rule because almost nothing outside our process accepts a string. Files, sockets, hash functions, encryption APIs and message queues all take byte[], so text has to become bytes before it leaves the application.

The default answer in C# is Encoding.UTF8.GetBytes(). It applies UTF-8 and returns a new byte array sized to fit.

Which encoding we pick is the part that matters. The same string produces different bytes under UTF-8, ASCII, UTF-16 and Latin-1, and whatever reads those bytes later has to use the same encoding to get the original text back.

Bytes carry no record of the encoding that produced them. That is why the encoding is a decision we make once, write down, and apply at both ends rather than something we can recover later.

Text is not the only source of bytes, of course. The same byte array can arrive when the bytes come from a stream, or from reading a file straight into a byte array, and everything we say here about encodings applies the moment those bytes have to become text again.

Knowing this, let’s see the various methods for converting a string to a byte array.

Before We Begin

For all of the code samples in this article, let’s create a string literal that we’ll use to convert to a byte array:

var message = "Welcome to CodeMaze!";

With this string, we expect the same output for each conversion method:

87,101,108,99,111,109,101,32,116,111,32,67,111,100,101,77,97,122,101,33

How Does Encoding.GetBytes() Convert a String?

Encoding.GetBytes() walks the string and writes each character’s encoded form into a new byte array.

UTF-8 is variable width. An ASCII character takes one byte, an accented Latin character takes two, most CJK characters take three, and an emoji takes four. So the byte array is usually longer than the string, and message.Length is not the size of the buffer we need.

Encoding.GetByteCount() gives that size. Encoding.GetBytes(string, Span<byte>) fills a buffer we already own, and Encoding.TryGetBytes() does the same without throwing when the buffer turns out to be too small.

Characters the encoding cannot represent do not throw by default. They are replaced, and for Encoding.ASCII the replacement is a question mark, so café comes back as caf? with no exception and no warning.

GetBytes() never writes a byte order mark. The three BOM bytes live behind GetPreamble(), and writing them is our job when a consumer expects one.

There are several encoding options and we’ll talk about choosing the right one later on, but now let’s see how to do the conversion using the UTF8 encoding option:

static byte[] ConvertStringToUTF8Bytes(string message)
{
    return Encoding.UTF8.GetBytes(message);
}

We define a static method and promptly return the byte array which now holds the encoded string. It does this by using the Encoding.GetBytes() method that performs the UTF8 encoding on our message variable, converting the string’s characters into their corresponding byte representations.

Which Encoding Should We Choose?

Selecting the “right” encoding for converting strings to byte arrays largely depends on context. Factors like the target platform, audience, storage and transmission efficiency, and interoperability, should guide our decision.

EncodingBytes for "café &#128512;" (7 chars)CoversUse when
Encoding.UTF810All of Unicode, 1 to 4 bytes per characterThe default: web, JSON, files, network protocols
Encoding.ASCII7, with ? for é and for the emojiU+0000 to U+007FA legacy ASCII-only consumer
Encoding.Latin17, with é intact and ? for the emojiU+0000 to U+00FFAn ISO-8859-1 consumer; this is the same encoding GetEncoding("ISO-8859-1") returns
Encoding.Unicode (UTF-16LE)14All of Unicode, 2 or 4 bytes per characterInterop with an API that expects UTF-16
Encoding.UTF3224All of Unicode, 4 bytes per characterFixed-width indexing; rare

If none of these fit because the bytes have to survive a change of encoding, we can build a byte representation that does not depend on an encoding instead.

What Happens When We Cast Characters to Bytes?

Casting characters to bytes offers a direct approach for string-to-byte-array conversion, but it’s crucial to note its limitations.

The cast keeps only the low byte of each character. Characters from U+0000 to U+00FF survive, so é (U+00E9) becomes 233. Everything above that is truncated without an error: Ā (U+0100) becomes 0, and an emoji, which C# stores as two surrogate characters, becomes two unrelated bytes. Converting "café 😀" this way produces 99,97,102,233,32,61,0, which reads back as café = followed by a null byte.

Let’s see how to use this technique for conversion:

static byte[] ConvertStringToByteArrayUsingCasting(string message)
{
    var byteArray = new byte[message.Length];

    for (int i = 0; i < message.Length; i++)
    {
        byteArray[i] = (byte)message[i];
    }

    return byteArray;
}

We initialize a byte array with a size equal to the length of the string. This creates a byte array with enough space to hold each string character as a byte value. Then we iterate through each character in the string, allowing us to process each character individually.

Within the loop, we assign each character to its corresponding index in the byte array. This effectively converts each character to its underlying byte value and stores it in the byte array.

Finally, we return the populated byte array. This array now contains the byte representation of the original string, where each character’s code point is stored as a single byte, which is only possible for the first 256 code points.

When Does Convert.ToByte() Throw Instead of Converting?

This method is part of the Convert class and it offers various data type conversion utilities. Similar to the previous method, it provides a direct way to convert a single character to its byte value. It is however not designed for converting entire strings to byte arrays.

Conversion using this technique is straightforward:

static byte[] ConvertStringToByteArrayUsingConvertToByte(string message)
{
    var byteArray = new byte[message.Length];

    for (int i = 0; i < message.Length; i++)
    {
        byteArray[i] = Convert.ToByte(message[i]);
    }

    return byteArray;
}

While this code shares a similar structure with the previous example, it employs the Convert.ToByte() method to perform the character-to-byte conversion. This approach offers a more explicit and potentially more readable way to convert characters to their corresponding byte values.

Unlike the cast, Convert.ToByte() refuses rather than truncates: any character above U+00FF throws an OverflowException, which makes it the safer of the two loops even though it is the slower one.

Which Encodings Does Encoding.GetEncoding() Support?

This method obtains an Encoding object for a specific encoding, which can then be used for various encoding-related operations, including converting strings to byte arrays using GetBytes().

We reach for GetEncoding() when something outside our control has already picked an encoding for us: a fixed-width mainframe export, an old CSV, a protocol that predates Unicode.

Let’s explore using this method:

static byte[] ConvertStringToByteArrayUsingEncoding(string message)
{
    var encoding = Encoding.GetEncoding("ISO-8859-1");
    var byteCount = encoding.GetByteCount(message);
    var byteArray = new byte[byteCount];

    encoding.GetBytes(message, byteArray);

    return byteArray;
}

Our method employs encoding to transform a string into its corresponding byte array by obtaining a specific instance of the ISO-8859-1 encoding using the Encoding.GetEncoding() method. This encoding is crucial for determining how characters within the string are mapped to their byte values.

Next, it meticulously calculates the exact number of bytes required to accommodate the encoded string with encoding.GetByteCount() and constructs a byte array with the calculated byte count via new byte[].

It translates the string characters into their corresponding byte values while storing the encoded bytes within the provided array. In the end, it returns the now-populated byte array.

Encoding.GetEncoding() only resolves the encodings .NET registers by default, which is a short list of seven: UTF-8, UTF-16 and UTF-32 in both byte orders, ASCII and Latin-1. ISO-8859-1 is on it, which is why the code above works. Windows code pages such as 1252 are not: Encoding.GetEncoding(1252) throws a NotSupportedException telling us to register a provider, and Encoding.GetEncoding("windows-1252") throws an ArgumentException instead, because the name is not one it knows. Calling Encoding.RegisterProvider(CodePagesEncodingProvider.Instance) once at startup restores them all.

That is the documented behaviour and not a quirk of our sample. Microsoft’s reference for Encoding.GetEncoding() states it directly: “In .NET Core, the GetEncoding method returns the encodings natively supported by .NET Core.”

Which Conversion Method Is Fastest?

Now that we’ve explored various methods for converting a string into a byte array, let’s put them to the test. We’ll analyze their performance using benchmarks, starting with a short string and then tackling a longer one.

For the short string, we’ll use our initial message value and add some extra text for the long one:

Welcome to CodeMaze, your one-stop destination for mastering all things .NET and C#! Explore a comprehensive learning experience tailored to your programming journey.

Let’s look at the short string benchmarks:

| Method                                | Mean     | Error    | StdDev   | Gen0   | Allocated |
|-------------------------------------- |---------:|---------:|---------:|-------:|----------:|
| ConvertShortMessageUsingCasting       | 12.84 ns | 0.553 ns | 1.586 ns | 0.0057 |      48 B |
| ConvertShortMessageUsingConvertToByte | 13.08 ns | 0.309 ns | 0.330 ns | 0.0057 |      48 B |
| ConvertShortMessageToUTF8Bytes        | 19.26 ns | 0.539 ns | 1.572 ns | 0.0057 |      48 B |
| ConvertShortMessageUsingGetEncoding   | 52.66 ns | 0.985 ns | 0.922 ns | 0.0057 |      48 B |

Next, we have the long string benchmarks:

| Method                               | Mean     | Error    | StdDev   | Median   | Gen0   | Allocated |
|------------------------------------- |---------:|---------:|---------:|---------:|-------:|----------:|
| ConvertLongMessageToUTF8Bytes        | 33.68 ns | 0.725 ns | 1.986 ns | 32.96 ns | 0.0229 |     192 B |
| ConvertLongMessageUsingGetEncoding   | 65.94 ns | 1.345 ns | 2.527 ns | 66.22 ns | 0.0229 |     192 B |
| ConvertLongMessageUsingCasting       | 78.97 ns | 1.532 ns | 2.385 ns | 78.99 ns | 0.0229 |     192 B |
| ConvertLongMessageUsingConvertToByte | 87.19 ns | 0.972 ns | 0.861 ns | 87.55 ns | 0.0229 |     192 B |

Measured with BenchmarkDotNet 0.15.8 on .NET 10, casting individual characters is still the fastest way to convert a short string, with Convert.ToByte() so close behind that the two overlap inside their error margins. For the long string the ordering flips: Encoding.GetBytes() is more than twice as fast as either loop, and Encoding.GetEncoding() follows it. Every method allocates the same 48 and 192 bytes, so the only thing separating them here is time.

Which Method Should We Use?

Encoding.UTF8.GetBytes() is the answer unless something outside our code has already chosen a different encoding. The three alternatives are worth knowing mainly for what they do to characters they cannot represent, which is where they differ from each other far more than they differ in speed.

MethodRange it handles correctlyOutside that rangeUse when
Encoding.UTF8.GetBytes(s)All of UnicodeNothing falls outsideThe default, for anything leaving the process
Encoding.ASCII.GetBytes(s)U+0000 to U+007FReplaced with ? (byte 63), silentlyThe consumer is ASCII-only and the loss is acceptable
Encoding.Latin1.GetBytes(s)U+0000 to U+00FFReplaced with ? (byte 63), silentlyAn ISO-8859-1 consumer requires it
Encoding.GetEncoding(name).GetBytes(s)Whatever the named encoding coversThe encoding's fallback, usually ?A named legacy encoding is required, and the provider is registered
Casting (byte)c in a loopU+0000 to U+00FFTruncated to the low byte, silently: U+0100 becomes 0Not recommended
Convert.ToByte(c) in a loopU+0000 to U+00FFThrows OverflowExceptionSingle characters, where a loud failure is what we want
"text"u8 literalAll of Unicode, at compile timeNot applicableThe string is a compile-time constant

How Do We Convert a Byte Array Back to a String in C#?

Encoding.GetString() is the other half of the pair. It takes a byte array and the same encoding, and returns the original string.

The encoding has to match. Bytes written as UTF-8 and read back as ASCII or Latin-1 produce a different string, and nothing in the byte array reports the mistake, so the encoding travels between the two ends as an agreement rather than as data.

Bytes that are not valid in the chosen encoding are replaced rather than rejected. UTF-8 turns each invalid sequence into the replacement character U+FFFD. To fail loudly instead, we construct new UTF8Encoding(false, true), whose decoder throws a DecoderFallbackException.

Only a matching round trip is guaranteed. Text encoded as UTF-8 and decoded as UTF-8 comes back identical. Text pushed through ASCII loses every character ASCII cannot represent, and no decoder can bring those characters back, because the bytes that described them were never written.

The method itself is a single line:

static string ConvertUTF8BytesToString(byte[] byteArray)
{
    return Encoding.UTF8.GetString(byteArray);
}

The encoding on this line has to be the one that produced the bytes, and that is the only thing this method can get wrong.

Plain text is not the only destination for a byte array. We can encode those bytes as Base64 for transport, turn the byte array into a hexadecimal string for display or hashing, or work the other way, starting from a hexadecimal string instead. And if the bytes are on their way somewhere untrusted, encrypting the resulting bytes is the step that belongs between the two conversions.

Conclusion

In this article, we discussed the importance of converting strings to byte arrays, explored various conversion methods, and emphasized the significance of selecting the appropriate encoding and conversion method based on the context of our application.

Tested with .NET 10.