Working with arrays of strings and bytes
NumPy has a family of text and byte dtypes - fixed-width str_ (U), bytes_ (S), raw void (V), and the variable-width StringDType added in NumPy 2.0. NumSharp implements none of them. It has one character-adjacent dtype, Char, and for everything else the answer is: use .NET's own string, string[], and byte[] directly, and cross the boundary with np.frombuffer when you need bytes as an array.
This page explains what NumSharp offers, why the NumPy string dtypes are deliberately absent, and how to port code that used them.
The Char dtype
NPTypeCode.Char wraps .NET's System.Char - a 2-byte UTF-16 code unit. It exists so an array of individual characters can carry "these are characters, not ushorts" in the type system:
var letters = np.array(['a', 'b', 'c']);
letters.typecode; // NPTypeCode.Char
InfoOf<char>.Size; // 2 (actual memory footprint)
Char is not NumPy's 'c' / S1. NumPy's S1 is a one-byte bytestring; NumSharp's Char is a two-byte UTF-16 unit. Different size, different encoding, different semantics - porting NumPy bytestring code onto Char will almost always be wrong. Its kind is reported as 'S' for NumPy round-trip ergonomics, and it maps to <U1 when written to .npy, but it behaves like a 2-byte integer for arithmetic. Full details and the itemsize quirk are in Data types → Char.
NumPy string/bytes dtypes are not supported
Every NumPy text or byte dtype raises NotSupportedException when you try to construct or parse it:
| NumPy dtype | Character code | NumSharp |
|---|---|---|
str_ (Unicode, UCS-4 fixed width) |
U, <U10 |
throws |
bytes_ (null-terminated bytestring) |
S, a, \|S5 |
throws |
void (raw byte block) |
V, \|V7 |
throws |
object (boxed Python objects) |
O |
throws |
StringDType (variable-width UTF-8, NumPy 2.0) |
- | throws |
np.dtype("U5"); // throws NotSupportedException
np.dtype("S10"); // throws
np.dtype("V7"); // throws
The throw is deliberate - see Data types → why throw instead of silent approximation. Silently mapping S10 to string or U to Char would produce a differently-sized, differently-encoded array than the caller asked for, and corrupt any binary round-trip.
Why these are absent
NumPy's string dtypes are fixed-width, in-buffer text: an <U10 array reserves 10 UCS-4 code points per element inside the array's data buffer, and StringDType stores UTF-8 out-of-band with in-buffer pointers. Neither maps cleanly onto .NET, where:
stringis a heap object (UTF-16, variable length), not a fixed-width buffer slot;byte[]is the natural home for raw bytes;- there is no zero-cost way to store variable-length text inside NumSharp's unmanaged element buffer.
So rather than build a half-fidelity port, NumSharp leaves text to .NET's excellent native types and keeps NDArray numeric.
What to use instead
Text data → string / string[]
Keep text in ordinary .NET collections. There is no NDArray of strings; index and process them with LINQ or plain loops:
string[] labels = { "cat", "dog", "bird" };
var lengths = np.array(labels.Select(s => s.Length).ToArray()); // NDArray<int> of lengths
If you need per-character arrays, Char works for a single fixed-width column, but a jagged set of words is a string[], not an array dtype.
Byte streams → byte[] + np.frombuffer
For raw bytes, use byte[] and reinterpret with np.frombuffer when you want to view them as numbers:
byte[] raw = File.ReadAllBytes("data.bin");
var asBytes = np.frombuffer(raw, np.uint8); // NDArray<byte>, zero-copy view
var asFloats = np.frombuffer(raw, np.float32); // reinterpret the same bytes as float32
This is the NumSharp analog of NumPy's np.frombuffer on a bytestring, and the general interop bridge - see Any library via np.frombuffer.
Fixed-width byte records → parse yourself
NumPy code that used S/V to read a fixed-width binary field should read the byte[] and slice it with .NET (Encoding.ASCII.GetString, BinaryReader, Span<byte>), then build numeric NDArrays from the parsed values.
Porting cheat-sheet
| NumPy | NumSharp |
|---|---|
np.array(["hello", "world"]) (→ <U5) |
string[] { "hello", "world" } (plain .NET) |
np.array([b"hi"], dtype="S2") |
byte[] / np.frombuffer(bytes, np.uint8) |
arr.astype("U10") |
not supported - keep text as string |
np.char.upper(arr) |
labels.Select(s => s.ToUpper()) (LINQ on string[]) |
StringDType() array |
string[] / List<string> |
np.array(['a','b','c']) (single chars) |
np.array(['a', 'b', 'c']) → Char |
Troubleshooting
"np.dtype("U5") / "S10" threw NotSupportedException"
That's expected - NumSharp has no string/bytes dtypes. Keep the data in string/string[]/byte[]; the exception message names the alternative.
"I mapped a NumPy S1 array to Char and the bytes are wrong"
Char is 2-byte UTF-16, not NumPy's 1-byte S1. Use byte (np.uint8) for bytestring data.
"arr.astype(np.float64) failed on my Char array"
Char is treated as a 2-byte integer for numeric conversions; make sure the values are what you expect (code points), not text you meant to keep as string.
API reference
| Item | Behavior |
|---|---|
NPTypeCode.Char / np.dtype('a'..'z' as chars) |
2-byte UTF-16 code unit dtype; kind == 'S'; <U1 on .npy |
np.dtype("U…") / "S…" / "V…" / "O" |
NotSupportedException |
np.frombuffer(byte[], dtype) |
reinterpret raw bytes as a numeric array |
InfoOf<char>.Size |
2 (true footprint; note DType.itemsize reports 1) |
Related reading
- Data types - the full dtype set, the
Charquirks, and why unsupported dtypes throw. - Structured arrays - the other NumPy dtype family NumSharp does not implement.
- Any library via np.frombuffer - the byte-buffer bridge.
- NumPy strings-and-bytes guide - the upstream article.