System::Array Helpers
Use familiar array algorithms without mistaking a static native helper for the CLR array object model.
Role and representation
System::Array is a non-instantiable class of static algorithms over std::vector<T>. It is not a common base class for runtime array objects, does not erase an element type, and does not provide CLR array metadata. The owned sequence is the vector itself; Array supplies familiar operations such as sorting, copying, searching, clearing, and resizing.
The header is System/Array.hpp in Core.Base. Consumers select that component and link SharpRuntime::Core.Base. MaxLengthProperty() returns the compatibility constant intcs maximum; it is not a promise that the host can allocate that many elements.
API families
| Family | Operations | Result or mutation |
|---|---|---|
| Ordering | Sort, Reverse, BinarySearch | Sort and Reverse mutate; BinarySearch returns an index or complemented insertion point |
| Copy and size | Copy, ConstrainedCopy, Resize | Copy assigns into existing storage; Resize changes vector size |
| Value search | IndexOf, LastIndexOf | First/last matching index, or -1 |
| Predicate search | Exists, Find, FindLast, FindAll, FindIndex, FindLastIndex | Boolean, value, vector, or index |
| Bulk mutation | Clear, Fill | Assigns T{} or a supplied value, over all or a range |
| Projection/visit | ConvertAll, ForEach, TrueForAll | New vector, side effects, or Boolean |
| Views/factories | AsReadOnly, Empty<T> | Const reference to the same vector, or a new empty vector |
Most families have whole-vector and signed index/length or startIndex/count overloads. Custom sort and binary-search comparisons use std::function<int(const T&, const T&)>; predicate and action families likewise accept typed std::function callables.
Sorting and default comparison
Sort(vector) changes the vector in place. For ordinary non-floating types, the default path follows the element's relational operators. For float and double, Sharp Runtime's current comparison policy deliberately follows .NET default-comparer behavior: NaN orders before every numeric value, including negative infinity, and two NaNs occupy the same comparison class. The implementation moves NaNs to the front before sorting the remaining values so the comparator given to std::sort remains a strict weak ordering.
A custom comparison is authoritative. It must return negative, zero, or positive and must define a consistent ordering; Array cannot repair a contradictory callback. BinarySearch assumes the searched range was ordered with the same comparison. It does not verify that precondition. When no match exists, it returns the bitwise complement of the insertion point, so recover the position with ~result.
#include <System/Array.hpp>
std::vector<int> values{8, 3, 5, 1};
System::Array::Sort(values);
const auto found = System::Array::BinarySearch(values, 5);
const auto missing = System::Array::BinarySearch(values, 4);
const auto insertionPoint = ~missing;
Equality and search results
IndexOf, LastIndexOf, and the default binary search use the runtime's default equality/comparison policy. A NaN needle therefore finds a NaN element on equality-shaped surfaces. Other element types use their native equality and ordering operators.
Find and FindLast return T{} when nothing matches. That can be indistinguishable from a real default-valued element. Use FindIndex, which returns -1 on a miss, when presence must remain explicit. TrueForAll is vacuously true for an empty vector, but it still validates that its predicate is non-empty before returning.
Range validation and exceptions
Signed range metadata is validated before iterator or pointer arithmetic. Negative indices, counts, lengths, and out-of-bounds ranges raise Sharp Runtime argument exceptions; the current helpers route these failures through ArgumentOutOfRangeException, a member of the argument-exception hierarchy. A forward search may start exactly at size(), producing an immediate miss. Backward searches preserve the special empty-array rule that accepts a start index of -1 or 0 only with a zero count.
Every public callable-taking overload rejects an empty std::function with ArgumentNullException, even when the vector is empty or a sort/search would otherwise perform no comparisons. For range-plus-callback overloads, validation order is observable: forward FindIndex validates its range before its predicate, while FindLastIndex validates the predicate first to match the reference behavior encoded by the current tests.
Copy behavior and raw buffers
Vector Copy writes into an already-sized destination; it never grows that destination. Copying within the same vector is overlap-safe in either direction. Elements are assigned, not copied as raw bytes, so resource-owning values such as std::string retain valid independent ownership.
The raw-pointer overload also performs overlap-aware element assignment and rejects negative metadata. A null source or destination is rejected when length > 0; two null pointers with zero length are accepted as the native empty-range idiom. Unlike a vector, a pointer carries no capacity, so the method cannot prove either raw buffer is long enough. That obligation stays with the caller.
ConstrainedCopy delegates directly to the vector Copy overload. Templates establish one element type at compile time, so there is no runtime array-type mismatch check. The name also does not add transactional staging if an element assignment itself throws; do not infer the complete CLR atomic-copy contract.
Mutation, ownership, and invalidation
The caller owns the vector. Passing it by value copies its elements; passing by reference shares the same object only for the duration guaranteed by the caller. Resize uses std::vector::resize: shrinking destroys removed elements, growing default-constructs new ones, and reallocation can invalidate every pointer, reference, iterator, Span, Memory view, or ArraySegment tied to the old buffer. Sort, Reverse, Clear, and Fill preserve capacity but mutate values visible through live aliases.
AsReadOnly returns a const std::vector<T>& to the same vector. It is neither an owning wrapper nor a snapshot: mutations made through another non-const reference are visible, and destruction or invalidating reallocation of the owner leaves the reference unusable. Empty<T>, by contrast, returns a fresh empty vector each time rather than a shared singleton.
Practical example
#include <System/Array.hpp>
std::vector<int> source{10, 20, 30, 40};
std::vector<int> destination(4, -1);
System::Array::Copy(source, 1, destination, 0, 3);
// destination: 20, 30, 40, -1
System::Array::Fill(destination, 0, 1, 2);
// destination: 20, 0, 0, -1
const auto even = System::Array::FindAll<int>(
source, [](const int& value) { return value % 20 == 0; });
The destination is sized before copying. The predicate is a real callable, and FindAll returns an owning vector. No view into source survives the operation.
Differences from C# and .NET arrays
| C# / .NET | Sharp Runtime / C++ | Consequence |
|---|---|---|
T[] is a fixed-size managed reference object | The common representation is resizable, value-like std::vector<T> | Assignment copies elements; resize and reallocation are possible |
| Every index operation is bounds checked | Direct vector operator[] is unchecked; Array range methods validate their metadata | Use checked APIs or explicit validation at untrusted boundaries |
| Arrays carry runtime element/rank metadata | Template type is known at compile time; this helper is one-dimensional and vector-oriented | No general CLR Array reflection or covariance |
Array.Empty<T>() may reuse a singleton | Empty<T>() returns a new empty vector value | Do not rely on identity |
Array.AsReadOnly returns a collection wrapper | AsReadOnly returns a const reference to the vector | No ownership extension or immutable snapshot |
| Null array references are representable | A vector value is always present | Use an optional or pointer only when absence is part of the domain |
Template and portability caveats
The header does not express every element requirement as a C++ concept. Operations instantiate only when T supplies the required construction, assignment, equality, ordering, hashing, stream, or callable behavior. A type usable with Reverse may still fail to instantiate with Sort or Clear. Allocation failures and exceptions thrown by element operations or user callbacks propagate as ordinary C++ exceptions.