System::Array Helpers

Use familiar array algorithms without mistaking a static native helper for the CLR array object model.

Core.BaseStatic algorithmsSource-verified

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

FamilyOperationsResult or mutation
OrderingSort, Reverse, BinarySearchSort and Reverse mutate; BinarySearch returns an index or complemented insertion point
Copy and sizeCopy, ConstrainedCopy, ResizeCopy assigns into existing storage; Resize changes vector size
Value searchIndexOf, LastIndexOfFirst/last matching index, or -1
Predicate searchExists, Find, FindLast, FindAll, FindIndex, FindLastIndexBoolean, value, vector, or index
Bulk mutationClear, FillAssigns T{} or a supplied value, over all or a range
Projection/visitConvertAll, ForEach, TrueForAllNew vector, side effects, or Boolean
Views/factoriesAsReadOnly, 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 has a reduced native contract

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# / .NETSharp Runtime / C++Consequence
T[] is a fixed-size managed reference objectThe common representation is resizable, value-like std::vector<T>Assignment copies elements; resize and reallocation are possible
Every index operation is bounds checkedDirect vector operator[] is unchecked; Array range methods validate their metadataUse checked APIs or explicit validation at untrusted boundaries
Arrays carry runtime element/rank metadataTemplate type is known at compile time; this helper is one-dimensional and vector-orientedNo general CLR Array reflection or covariance
Array.Empty<T>() may reuse a singletonEmpty<T>() returns a new empty vector valueDo not rely on identity
Array.AsReadOnly returns a collection wrapperAsReadOnly returns a const reference to the vectorNo ownership extension or immutable snapshot
Null array references are representableA vector value is always presentUse 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.