Getting & Setting Values
Reading and writing array data is where you spend most of your time with an NDArray. NumPy offers roughly a dozen ways to do it - indexing, slicing, masks, fancy indices, fill, put, place, copyto, fill_diagonal, the flat iterator - and NumSharp mirrors all of them, with the same semantics and the same view/copy rules. This page is the map: every way to get a value, every way to set one, which converts and which doesn't, and which ones write through to shared memory.
It builds on NDArray (the anatomy and the basics), Dtypes (element types and coercion), and Broadcasting (shape rules, which also govern assignment). If a term here is unfamiliar, start there.
The rule that governs everything: views vs copies
Whether a get lets you set through it depends on one thing - did it return a view (shared memory) or a copy (independent memory)?
var a = np.arange(10);
var v = a["2:5"]; // slice → view - shares memory with a
v[0] = 999; // writes through: a[2] is now 999
var c = a["2:5"].copy(); // explicit copy - independent
c[0] = 0; // a[2] stays 999
| Access | Returns | Write-through? |
|---|---|---|
Plain slice - a["1:3"], a[0] of an N-D a, a["..., -1"], a.T |
view | yes - mutates the parent |
Element - an integer per axis: a[1, 2], a[0] of a 1-D a |
copy (NumPy's scalar, read-only) | no - write the element with the setter, a[1, 2] = v |
Fancy index (read) - a[idx] |
copy | no - but a[idx] = v (the setter) does touch the parent |
Boolean mask (read) - a[mask] |
copy | no - but a[mask] = v (the setter) does touch the parent |
Broadcasted view - np.broadcast_to(a, shape) |
read-only view | writes throw (assignment destination is read-only) |
The asymmetry on fancy/boolean indexing is deliberate and matches NumPy: reading a[mask] gathers into a fresh array (writing into that array does nothing to a), but assigning a[mask] = value scatters back into a through the setter. See NDArray → Views vs Copies.
Reading and writing text
For S, U and StringDType arrays, use the descriptor-aware indexers and item():
var names = np.array(new[] { "cat", "dog" }, "U4");
string name = (string)names.item(0); // "cat"
names[1] = "zebras"; // "zebr": a U4 store truncates
var text = names.astype(np.dtypes.StringDType(na_object: null));
text[1] = null; // a missing value, not "None"
object value = text.item(1); // null
An S element is a byte[]; a U element is a string; a T element is a string or its configured NA object. NDArray.AsStringArray(a) materializes string values in C order; use item() / tolist() when missing objects must remain distinct from text.
GetString / SetString read and write a vector of Char code units as one string. They are not the element accessors for S, U or T. Nor can a typed raw accessor or Span<string> view the packed text storage.
NDCharArray adds another rule: c[0].item() returns the whitespace-stripped element, while c.item(0) returns its unstripped stored value. A stripped element is a read-only stand-in, not a write-through view; assign through c[0] = value to update the array. See Strings, bytes and chararrays.
Getting values
Whole sub-arrays - the indexer
Python's slice notation is passed as a string; separate integer arguments are coordinates.
var a = np.arange(20).reshape(4, 5);
a[0]; // np.a[0] first row - reduces a dim → (5,) view
a[-1]; // np.a[-1] last row
a[1, 2]; // np.a[1, 2] single element → 0-d NDArray
a["1:3"]; // np.a[1:3] rows 1-2 → (2, 5) view
a["1:3, :2"]; // np.a[1:3, :2] rows 1-2, first two cols → (2, 2) view
a["::2"]; // np.a[::2] every other row
a["::-1"]; // np.a[::-1] reversed first axis
a["..., -1"]; // np.a[..., -1] ellipsis + last column
Boolean and fancy indexing return copies:
var arr = np.array([10, 20, 30, 40, 50]);
var mask = arr > 20; // NDArray<bool>
arr[mask]; // arr[mask] → [30, 40, 50] (copy)
var idx = np.array([0, 2, 4]);
arr[idx]; // arr[idx] → [10, 30, 50] (copy)
arr[new[] {0, 2, 4}]; // same - a raw int[] as the SOLE index is fancy
a[new[]{0,2}]is fancy, not coordinate. A rawint[]/long[]used as the whole index selects rows 0 and 2 (shape(2, …)), matching NumPy'sa[[0, 2]]. Separate integersa[0, 2]stay coordinate access. For element/sub-array access by a coordinate array, usea.GetValue(0, 2)(see below), nota[coordArray].
Single elements - five accessors
Reading one element has five entry points, chosen by how many indices you have and whether you want conversion:
var a = np.arange(12).reshape(3, 4); // int64 (NumPy 2.x default integer)
// 1. Indexer - NumPy's scalar: a read-only 0-d NDArray holding a COPY of the element (NumPy 2.x's a[1, 2] is an np.int64)
NDArray elem = a[1, 2];
long v = (long)elem; // cast the 0-d out → converts
// 2. .item<T>() - flat index, CONVERTS if T differs from the dtype (NumPy parity)
long v2 = a.item<long>(6); // flat index 6 → row 1, col 2
double d2 = a.item<double>(6); // reads the int64 as a double (converts)
object bx = a.item(6); // untyped → boxed long
// 3. GetValue<T> - N-D coordinates; another T than the dtype's own type is NumPy's cast to T
long v3 = a.GetValue<long>(1, 2);
// 4. GetAtIndex<T> - flat index, the same conversion rule, no Shape math (fastest)
long v4 = a.GetAtIndex<long>(6);
// 5. Typed named accessors - GetDouble / GetInt32 / GetSingle / GetBoolean / GetDecimal / GetComplex
// N-D coordinates; the stored value when the dtype matches, NumPy's astype cast of it otherwise.
var f = np.zeros(4); // float64
double x = f.GetDouble(0); // the stored double
double y = a.GetDouble(1, 2); // 6.0: the int64 element, converted as astype converts it
Which to use:
| Accessor | Indices | Converts? | Use when |
|---|---|---|---|
a[i, j] (indexer) |
N-D | yes (cast the 0-d out) | NumPy-like ergonomics; you don't mind the 0-d detour (a fresh read-only copy, about 0.5 µs) |
a.item<T>(i) / a.item(i) |
flat | yes | porting NumPy code; reading across dtypes |
a.GetValue<T>(...) |
N-D | yes (NumPy's cast to T) |
coordinate access in typed code |
a.GetAtIndex<T>(i) |
flat | yes (NumPy's cast to T) |
hot loops - fastest, no Shape math |
a.GetDouble(...) etc. |
N-D | yes (NumPy's cast) | a non-generic call for a known result type |
The typed reads are fastest when T is the dtype's own type (long for int64): the element is then read as stored, with no conversion.
.item()with no arguments unwraps any size-1 array (0-d, 1-element 1-D, 1×1 2-D) to a scalar, and throws NumPy'sValueErrorotherwise (can only convert an array of size 1 to a Python scalar) - the NumPy 2.x replacement for the removednp.asscalar().
Reading flat, in order
foreach (var x in a.flat) { } // boxed elements, C-order (a.flat is a raveled view)
long s = (long)a.flatiter[5]; // flat C-order element (getter returns object → unbox; see below)
// Fast, unboxed - by reference (T is the dtype's own type: long for this int64 array):
long total = 0;
foreach (ref long e in np.nditer<long>(a)) total += e;
A ref T points into the array's memory, so np.nditer<T> cannot convert: np.nditer<double> over this int64 array throws ArgumentException. Iterate a.astype(np.float64) for doubles.
For high-throughput reads use np.nditer<T> (unboxed, by reference) or np.nditer_chunks<T> (a Span<T> per inner loop). See NDArray → Fast element iteration and NDIter.
Setting values
1. Elements
var a = np.arange(12).reshape(3, 4); // int64
a[1, 2] = 99; // indexer - converts the value to the array dtype
a.SetValue(99L, 1, 2); // N-D coordinates
a.SetAtIndex(99L, 6); // flat index
a.SetValue(2.5, 1, 3); // another type is cast to int64 with NumPy's cast: stores 2
var f = np.zeros((3, 4)); // float64
f.SetDouble(3.5, 1, 2); // typed setter
Every setter converts the value to the array's dtype with NumPy's cast, as the indexer does; a value of the dtype's own type is stored as it is, the fastest route. Mirror of the getter table above.
2. Slices - scalar fill and broadcast assignment
Assigning to a slice writes into the underlying array (slices are views). A scalar fills; a shaped right-hand side broadcasts:
var a = np.arange(20).reshape(4, 5);
a["1:3"] = 0; // a[1:3] = 0 fill rows 1-2 with 0
a[0] = 7; // a[0] = 7 fill first row (scalar broadcasts)
a[0] = np.zeros(5); // a[0] = np.zeros(5) assign a whole row
a["1:3, :"] = np.array([1,2,3,4,5]); // row vector broadcasts down the two rows
a["::2"] = -1; // every other row
The right-hand side must broadcast to the target's shape (see Broadcasting); a mismatch throws IncorrectShapeException.
3. Boolean mask
var arr = np.array([10, 20, 30, 40, 50]);
arr[arr > 20] = -1; // arr[arr > 20] = -1 → [10, 20, -1, -1, -1]
var mask = arr < 0; // [F, F, T, T, T]
arr[mask] = np.array([1, 2, 3]); // one value per True position → [10, 20, 1, 2, 3]
The value count must match the number of True positions (or be a single scalar, which fills all of them). Unlike the read arr[mask] (a copy), the assignment arr[mask] = … writes through to arr.
4. Fancy indices
var arr = np.array([10, 20, 30, 40, 50]);
arr[np.array([0, 2, 4])] = 0; // arr[[0,2,4]] = 0 → [0, 20, 0, 40, 0]
arr[np.array([0, 2, 4])] = np.array([1, 2, 3]); // one value per index
Duplicate indices follow last-write-wins, matching NumPy.
5. ndarray.fill - set every element in place
var a = np.zeros((3, 4));
a.fill(7); // a.fill(7) - every element = 7, IN PLACE, any layout
fill works across every memory layout (contiguous, F-order, sliced, transposed, negative-stride) and coerces the scalar to the dtype with NumPy's scalar-assignment rules. It returns void and mutates the array.
6. ndarray.put / np.put - scatter by flat index
var a = np.arange(5);
a.put(np.array([0, 2]), np.array([10, 20])); // a.put([0,2], [10,20]) → [10,1,20,3,4]
np.put(a, np.array([0, 2]), np.array([10, 20]));
np.put(a, np.array([4]), np.array([99]), "clip"); // out-of-range handled by mode
Indices are flat (C-order). mode controls out-of-range handling - "raise" (default; negative indices normalize once, then out-of-range throws), "wrap", or "clip" - matching NumPy. Values shorter than the index list are reused cyclically.
Pass the indices/values as
NDArray. Because a bare C#intconverts implicitly to bothlongandNDArray, the fully-literal single-index callnp.put(a, 4, 99)is ambiguous and won't compile - wrap them (np.put(a, np.array([4]), np.array([99]))), as above.
7. np.place - fill mask positions from a value list
var a = np.arange(6);
np.place(a, a > 2, np.array([100, 200])); // → [0, 1, 2, 100, 200, 100]
np.place(arr, mask, vals) writes vals into the True positions of mask, cycling through vals (not broadcasting) and truncating an over-long list - a.flat[mask] = vals semantics.
8. np.copyto - the general conditional copy
np.copyto(dst, src); // np.copyto(dst, src)
np.copyto(dst, src, where: mask); // only where mask is True
np.copyto(dst, src, casting: "unsafe"); // relax the cast rule
np.copyto(dst, src, casting = "same_kind", where = null) copies src into dst (broadcasting src to dst's shape), optionally gated by a boolean where mask. where must be bool and broadcasts to the output; masked-off positions keep their prior contents. casting follows NumPy's rule names ("no", "equiv", "safe", "same_kind", "unsafe").
9. np.fill_diagonal - set the diagonal in place
var m = np.zeros((4, 4));
np.fill_diagonal(m, 5); // np.fill_diagonal(m, 5) - 5 on the diagonal
np.fill_diagonal(m, np.array([1, 2, 3, 4])); // one value per diagonal slot
Values tile cyclically and truncate (they do not broadcast); wrap: true continues the diagonal on tall matrices. Returns void, mutates in place, and writes correctly through transposed / sliced / F-order / negative-stride views.
10. ndarray.flatiter - write through the flat iterator, any layout
var t = np.arange(6).reshape(2, 3).T; // a transposed (non-contiguous) view
t.flatiter[5] = 99; // t.flat[5] = 99 - writes through in logical C-order
t.flatiter["::2"] = 0; // slice assignment
t.flatiter[new[] {0, 2}] = np.array([7, 8]); // fancy assignment
arr.flatiter is NumSharp's flatiter (the type of NumPy's arr.flat). It reads and writes through to the base in logical C-order for every layout - including transposed, sliced, strided, negative-stride, and broadcast views. Negative flat indices wrap; out-of-range raises IndexError.
Why
flatiter, notflat? NumSharp'sarr.flatreturns a raveledNDArray, which copies for a non-contiguous layout - soarr.T.flat[i] = vwould be silently lost.arr.flatiteris the write-through accessor; prefer it whenever you assign by flat index into a view.
Value coercion on assignment (NEP50)
When a scalar is written to an array (fill, flatiter set, scalar indexer assignment), NumSharp coerces it to the dtype with NumPy 2.x's weak/strong scalar rules:
- A C# primitive is a weak scalar (NumSharp's analog of a Python literal).
- Into an integer dtype it is range-checked: an out-of-range value raises
OverflowError("Python integer 300 out of bounds for int8") rather than wrapping. A float source truncates toward zero first, then the truncated value is checked;NaNraisesValueError,±infraisesOverflowException, a complex source raisesTypeError. - Into a float/complex dtype it casts, saturating to
±infon overflow (float32.fill(1e300)→inf).
- Into an integer dtype it is range-checked: an out-of-range value raises
- A 0-d
NDArrayis a strong scalar - it wraps on cast, as NumPy's 0-d array does (a.fill(np.array(300, np.int32))stores 44). Usenp.array(v)(a 0-d array of the C# value's type:np.array(300)is int32) when you need the wrapping cast. NumPy 2.5.4 range-checks its scalar objects (np.int32(300), an elementb[j]) like Python ints here (OverflowError: Python integer 300 out of bounds for int8); NumSharp's scalars (NDArray.Scalar(...), an element, a reduction's result) still store with the 0-d array's wrapping cast, and a C# primitive takes the range-checking role. - A higher-rank array assigned as a scalar is a sequence →
ValueError("setting an array element with a sequence.").
var a = np.zeros(3, dtype: np.int8);
a.fill(300); // OverflowError - weak int out of range for int8
a.fill(np.array(300)); // OK - a 0-d int32 array wraps → stores 44
In-place vs. reassignment
C# compound operators (+=, *=, …) are not in-place on an NDArray - the compiler expands a += 1 into a = a + 1, producing a new array and rebinding the variable. Other references to the original do not see the change:
var x = np.array([1, 2, 3]);
var alias = x;
x += 10; // x → NEW array [11, 12, 13]
// alias // still [1, 2, 3] - unlike NumPy!
To modify the array's memory in place, assign through a slice - the setter path:
x[":"] = x + 1; // in-place: rewrites x's buffer; alias sees it too
a["1:3"] += 5; // still expands to a["1:3"] = a["1:3"] + 5, but the LHS is a view,
// so the result is written back into the parent
See NDArray → Compound assignment.
NumPy → NumSharp cheat-sheet
| NumPy writer | Form | NumSharp equivalent |
|---|---|---|
arr[0,1] = 5 |
[] element |
arr[0,1] = 5 (value → 0-d NDArray, converts); or arr.SetValue(5L,0,1) (exact) / arr.SetDouble(5,0,1) (exact, double array) |
arr[1:3] = 0 |
[] slice |
arr["1:3"] = 0 |
arr[mask] = 0 |
[] bool mask |
arr[mask] = 0 |
arr[[0,2]] = [1,2] |
[] fancy |
arr[np.array([0,2])] = np.array([1,2]) (or a raw int[] index) |
arr.fill(5) |
method | arr.fill(5) |
arr.put([k],[v]) / np.put |
method / func | arr.put(...) / np.put(...) |
np.place(arr, mask, vals) |
func | np.place(...) |
np.copyto(dst, src, where=) |
func | np.copyto(dst, src, where: mask) |
np.fill_diagonal(arr, v) |
func | np.fill_diagonal(...) |
arr.flat[5] = v |
flat [] |
arr.flatiter[5] = v (write-through, any layout) |
np.putmask(arr, mask, v) |
func | not present - use np.place(arr, mask, v) or np.copyto(arr, v, where: mask) |
arr.itemset(k, v) |
method | legacy / removed in NumPy 2.0. NumSharp keeps an itemset, but it diverges: it takes coordinates (itemset(coords, value)), not a flat index, and does not reliably cast the value to the dtype. Don't use it - write arr[0,1] = v or arr.SetValue(v, 0, 1) instead. |
Reading has the mirror set - the indexer (a[i,j]), a.item<T>(i), a.GetValue<T>(...), a.GetAtIndex<T>(i), the typed named getters, and a.flatiter[i].
Troubleshooting
"My array changed when I modified a slice!"
Slices are views. a["1:3"] shares memory with a. Force a copy: a["1:3"].copy().
"ValueError: assignment destination is read-only"
You're writing into a broadcasted view (a stride-0 dimension), which is read-only to prevent cross-row corruption. Copy first: var w = b.copy(); w["..."] = value;. See Broadcasting → Memory Behavior.
"GetInt32 / SetDouble changed my value"
The typed accessors (GetDouble/SetDouble/GetInt32/…, GetValue<T>, GetAtIndex<T>, SetAtIndex<T>) convert with NumPy's cast when T is not the array's own type: GetInt32 of a float64 2.9 is 2, and SetDouble(3.5, …) into an int64 array stores 3. Read and write with the dtype's own type (a.dtype.type) to keep values exact.
"OverflowError: Python integer … out of bounds"
A weak C# scalar didn't fit the target integer dtype. That's NumPy 2.x behavior - weak scalars range-check instead of wrapping. Pass a 0-d array to wrap: a.fill(np.array(v)), or widen the dtype.
"a += 1 didn't update another reference"
C# compound assignment rebinds the variable; it doesn't mutate. Use a[":"] = a + 1 for in-place. See In-place vs. reassignment.
"arr.itemset(5, v) set the wrong element"
NumSharp's legacy itemset takes coordinates (itemset(coords, value)), not NumPy's flat index. Use arr.flatiter[5] = v for a flat write, or arr[i, j] = v for coordinates.
API Reference
Getters
| Member | Indices | Converts? | Returns |
|---|---|---|---|
a[i, j] / a["1:3"] / a[mask] / a[idx] |
mixed | - | NDArray (0-d for one element) |
a.item(i) / a.item<T>(i) |
flat | yes | object / T |
a.item() / a.item<T>() |
- | yes | size-1 array → scalar |
a.GetValue<T>(...) / a.GetValue(...) |
N-D | yes (NumPy's cast to T) / boxed |
T / object |
a.GetAtIndex<T>(i) / a.GetAtIndex(i) |
flat | yes (NumPy's cast to T) / boxed |
T / object |
a.GetDouble/GetInt32/GetSingle/GetBoolean/GetDecimal/GetComplex(...) |
N-D | yes (NumPy's cast) | the named scalar type |
a.flatiter[i] / ["1:3"] / [idx] |
flat | yes (set) | scalar / NDArray |
a.flat |
- | - | raveled NDArray view |
Setters
| Member | Scope | Notes |
|---|---|---|
a[i, j] = v |
element | converts to dtype (NEP50 coercion) |
a["1:3"] = v / a[0] = row |
slice / row | broadcasts RHS; writes through the view |
a[mask] = v |
masked | writes through to the parent |
a[idx] = v |
fancy | last-write-wins on duplicates |
a.SetValue<T>(v, ...) / a.SetValue(v, ...) |
element (N-D) | NumPy's cast to the dtype; fastest when T is the dtype's own type |
a.SetAtIndex<T>(v, i) |
element (flat) | NumPy's cast to the dtype; no Shape math |
a.SetDouble(v, ...) |
element (N-D) | NumPy's cast of the double to the dtype |
a.fill(v) |
whole array | in place, any layout, NEP50 coercion |
a.put(indices, values, mode) / np.put(...) |
flat scatter | mode: "raise"/"wrap"/"clip" |
np.place(arr, mask, vals) |
masked | cyclic fill of the True positions |
np.copyto(dst, src, casting, where) |
conditional copy | where bool mask; casting rule |
np.fill_diagonal(a, val, wrap) |
diagonal | in place, cyclic values |
a.flatiter[i] = v |
flat | write-through, any layout |
See also: NDArray, Dtypes, Broadcasting, NDIter, Exceptions.