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], a["..., -1"], a.T |
view | yes - mutates the parent |
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.
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 - returns a 0-d NDArray (a single element is still an array, like NumPy 2.x)
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, EXACT dtype (T must equal the dtype)
long v3 = a.GetValue<long>(1, 2);
// 4. GetAtIndex<T> - flat index, EXACT dtype, no Shape math (fastest)
long v4 = a.GetAtIndex<long>(6);
// 5. Typed named accessors - GetDouble / GetInt32 / GetSingle / GetBoolean / GetDecimal / GetComplex
// EXACT dtype: they read the matching typed slice directly and do NOT convert.
var f = np.zeros(4); // float64
double x = f.GetDouble(0); // OK - array IS double
// a.GetDouble(0); // on an int64 array this REINTERPRETS the raw bytes as a double - a silent,
// // wrong value (no throw, no conversion). Use a.item<double>(0) to convert.
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.item<T>(i) / a.item(i) |
flat | yes | porting NumPy code; reading across dtypes |
a.GetValue<T>(...) |
N-D | no (exact) | coordinate access in typed code |
a.GetAtIndex<T>(i) |
flat | no (exact) | hot loops - fastest, no Shape math |
a.GetDouble(...) etc. |
N-D | no - dtype must match | you already know the dtype and want a non-generic call (reinterprets raw bytes if it doesn't) |
.item()with no arguments unwraps any size-1 array (0-d, 1-element 1-D, 1×1 2-D) to a scalar, and throwsIncorrectSizeExceptionotherwise - 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:
double total = 0;
foreach (ref double e in np.nditer<double>(a)) total += e;
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 - EXACT dtype (value must be long here)
a.SetAtIndex(99L, 6); // flat index - EXACT dtype
var f = np.zeros((3, 4)); // float64
f.SetDouble(3.5, 1, 2); // typed setter - EXACT dtype (writes raw double bytes)
The indexer converts; SetValue<T> / SetAtIndex<T> / SetDouble (and the other typed setters) write the value's bytes directly and require the value to match the dtype. 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
OverflowException("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, matching annp.int64scalar. UseNDArray.Scalar<T>(v)when you need the strong behavior. - 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); // OverflowException - weak int out of range for int8
a.fill(NDArray.Scalar<int>(300)); // OK - strong scalar 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().
"NumSharpException: 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.
"GetDouble returned a nonsense number (or SetDouble corrupted the array)"
The typed named accessors (GetDouble/SetDouble/GetInt32/…) require the array to actually be that dtype - they read/write the raw bytes directly and do not convert. On a mismatched dtype they silently reinterpret the bytes (e.g. int64 value 6 read via GetDouble comes back as 2.96e-323), and SetDouble into a narrower dtype writes 8 bytes at the wrong stride and corrupts memory. Use a.item<double>(i) to read as double, or the indexer / astype to convert.
"OverflowException: 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 strong scalar to wrap: a.fill(NDArray.Scalar<int>(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 | no / boxed | T / object |
a.GetAtIndex<T>(i) / a.GetAtIndex(i) |
flat | no / boxed | T / object |
a.GetDouble/GetInt32/GetSingle/GetBoolean/GetDecimal/GetComplex(...) |
N-D | no - dtype must match | the named scalar type (reinterprets on mismatch) |
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) | exact dtype |
a.SetAtIndex<T>(v, i) |
element (flat) | exact dtype |
a.SetDouble(v, ...) |
element (N-D) | exact dtype (double array) |
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.