In C#, converting a string to a byte array is a frequent requirement when dealing with file I/O, network sockets, serialization, or cryptographic operations. The process involves translating the character data of a string into a sequence of bytes that can be stored, transmitted, or processed by lower‑level APIs. Understanding the various approaches, their performance characteristics, and the impact of character encoding is essential for writing dependable and efficient code.
Why Convert a String to a Byte Array?
- Interoperability – Many APIs (e.g.,
System.IO.FileStream,System.Net.Sockets.TcpClient) expect data asbyte[]rather thanstring. - Serialization – When persisting objects to disk or sending them over a network, strings must be serialized into bytes.
- Cryptographic operations – Hash functions and encryption algorithms work on raw byte sequences.
- Encoding conversion – You may need to transform a string from one encoding (e.g., UTF‑8) to another (e.g., ASCII) for compatibility with legacy systems.
Common Methods
Using Encoding.UTF8.GetBytes
The most straightforward way to convert a string to a byte array is with the Encoding class. The UTF‑8 encoding is the default for most modern applications because it can represent any Unicode character while remaining space‑efficient for ASCII text.
string text = "Hello, world!";
byte[] bytes = Encoding.UTF8.GetBytes(text);
Key points
Encoding.UTF8is static and thread‑safe.- The resulting byte array includes a Byte Order Mark (BOM) only if you explicitly write it;
GetBytesdoes not add a BOM. - For strings with characters outside the ASCII range, UTF‑8 produces multiple bytes per character, which is expected.
Using Encoding.ASCII.GetBytes
If you know the string contains only ASCII characters (code points 0‑127), Encoding.ASCII provides a compact one‑byte‑per‑character representation It's one of those things that adds up..
string asciiText = "ASCII only";
byte[] asciiBytes = Encoding.ASCII.GetBytes(asciiText);
When to use
- Legacy systems that expect 7‑bit data.
- Scenarios where bandwidth is critical and the character set is limited.
Caution – Any non‑ASCII character will be replaced with a ? (0x3F), potentially causing data loss.
Using Convert.FromBase64String
When a string is already encoded in Base64 (common in email attachments or JSON web tokens), you can decode it directly to a byte array.
string base64 = "SGVsbG8gd29ybGQ=";
byte[] data = Convert.FromBase64String(base64);
Typical use cases
- Decoding tokens, checksums, or any payload that has been serialized to Base64.
- Interacting with APIs that return binary data as a Base64 string.
Using Span<T> and Memory<T> for Zero‑Copy Conversions
For high‑performance scenarios, you can avoid allocating a new byte array by using Span<byte> or Memory<byte> together with Encoding.GetEncoding.
string text = "Performance matters";
Span buffer = stackalloc byte[Encoding.UTF8.GetMaxByteCount(text.Length)];
int length = Encoding.UTF8.GetBytes(text,
```csharp
string text = "Performance matters";
Span buffer = stackalloc byte[Encoding.UTF8.GetMaxByteCount(text.Length)];
int length = Encoding.UTF8.GetBytes(text, buffer);
// `buffer[0..length]` now holds the UTF‑8 encoded bytes without a heap allocation.
ReadOnlySpan utf8Bytes = buffer.Slice(0, length);
Why Span<T>/Memory<T> matters
- Zero‑copy: The bytes are written directly into stack‑allocated memory (or a pooled
Memory<byte>), eliminating the extra GC pressure thatEncoding.GetBytes(string)normally incurs. - Slicing: After the encoding call you can obtain a
ReadOnlySpan<byte>orReadOnlyMemory<byte>that references just the used portion, which can be passed to APIs that accept spans (e.g.,System.IO.Pipelines,System.Text.Json, or native interop viaMarshal.Copy). - Reusability: By renting a buffer from
ArrayPool<byte>.Sharedand wrapping it in aMemory<byte>, you can reuse the same storage across many conversions, further reducing allocations in tight loops.
Alternative approaches worth noting
| Method | When to use | Trade‑offs |
|---|---|---|
BitConverter.GetBytes(value) |
Converting primitive numeric types to bytes (little‑endian). | Only works for value types; endian‑sensitive. |
Encoding.Unicode.GetBytes / Encoding.UTF32.GetBytes |
When you need UTF‑16 or UTF‑32 byte streams (e.g., Windows API interop). | Larger output; UTF‑16 may require handling surrogate pairs. |
System.Text.Encoding.GetEncoder().GetBytes |
Incremental encoding of large streams (e.g., reading a file chunk‑by‑chunk). | Requires managing encoder state and flush. |
MemoryMarshal.Cast |
When you already have a byte[] and need to view it as another primitive type without copying. |
Unsafe if alignment or size assumptions are violated. |
Best‑practice checklist
- Prefer UTF‑8 unless a legacy system explicitly demands another encoding.
- Validate input when using ASCII‑based encodings to avoid silent data loss (
?substitution). - Use
Span<byte>/Memory<byte>for performance‑critical paths; fall back to the simpleEncoding.GetBytes(string)when readability outweighs micro‑optimizations. - Handle BOMs explicitly if your protocol requires them—
GetBytesnever adds a BOM, but you can prepend one manually (new byte[] {0xEF,0xBB,0xBF}). - Dispose encoders/decoders only when you obtain them via
GetEncoder()/GetDecoder(); the staticEncodinginstances themselves are thread‑safe and require no disposal.
Conclusion
Converting strings to byte arrays in .NET is a common task that touches serialization, cryptography, and interop. While Encoding.UTF8.GetBytes remains the go‑to for most scenarios due to its simplicity and correctness, understanding the alternatives—ASCII for strict 7‑bit data, Base64 decoding for text‑encoded binary, and span‑based zero‑copy techniques for high‑throughput code—allows developers to choose the right tool for the job. By matching the encoding to the data’s characteristics and leveraging modern memory‑management primitives when performance matters, you can achieve both correctness and efficiency in your applications.