Fluentsoft.Ref.Matrix.Demo 2.0.7

dotnet add package Fluentsoft.Ref.Matrix.Demo --version 2.0.7
                    
NuGet\Install-Package Fluentsoft.Ref.Matrix.Demo -Version 2.0.7
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Fluentsoft.Ref.Matrix.Demo" Version="2.0.7" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Fluentsoft.Ref.Matrix.Demo" Version="2.0.7" />
                    
Directory.Packages.props
<PackageReference Include="Fluentsoft.Ref.Matrix.Demo" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Fluentsoft.Ref.Matrix.Demo --version 2.0.7
                    
#r "nuget: Fluentsoft.Ref.Matrix.Demo, 2.0.7"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Fluentsoft.Ref.Matrix.Demo@2.0.7
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Fluentsoft.Ref.Matrix.Demo&version=2.0.7
                    
Install as a Cake Addin
#tool nuget:?package=Fluentsoft.Ref.Matrix.Demo&version=2.0.7
                    
Install as a Cake Tool

RefMatrix

RefMatrix is a source-based, zero-allocation matrix view built around ref struct-friendly data access.

The goal is to represent matrix-shaped views over existing storage without copying data and without forcing heap allocation. The matrix does not own the underlying memory. It delegates storage access to a provider and uses cursor projections to translate matrix coordinates into the provider's cursor space.

This makes the model suitable for high-performance code that works with:

  • Span<T>-backed data;
  • array-backed data;
  • custom providers;
  • nested submatrices;
  • row, column, and block views;
  • writable ref access to cells;
  • composable cursor transformations.

Core idea

The current matrix model is split into composable layers:

  1. Provider

    Provides access to underlying storage in its own cursor space.

    Examples:

    • SpanRefProvider<T> with int cursors;
    • ArrayRefProvider<T> with int cursors;
    • custom providers;
    • another RefBoundedSource used as a provider for nested views.
  2. Projection

    Maps matrix cursors to provider cursors.

    For example:

    MatrixCursor -> int
    MatrixCursor -> For2Cursor -> int
    MatrixCursor -> MatrixCursor -> provider cursor
    
  3. Bounds

    Describe the visible local matrix shape.

    MatrixBounds stores only the current view dimensions:

    RowCount
    ColumnCount
    
  4. Matrix view

    RefMatrix<T, TProvider, TProviderCursor, TProjection> combines:

    provider + projection + MatrixBounds
    

    Internally it uses RefBoundedSource.

The same backing storage can be exposed through many matrix views without copying the underlying data.


Coordinate spaces

RefMatrix deliberately separates coordinate spaces.

Local matrix coordinates

Local coordinates are coordinates inside the current matrix view or submatrix view.

matrix[row, column]

For every matrix view, local coordinates start at:

Row = 0, Column = 0

The public indexer order is:

matrix[row, column]

not:

matrix[column, row]

This follows the same order as two-dimensional arrays:

array[row, column]

Linear index

A linear index is an index inside the current matrix view in row-major order.

For a 3 x 2 matrix:

0 1 2
3 4 5

The linear indexer uses Index:

ref var first = ref matrix[Index.FromStart(0)];
ref var last = ref matrix[^1];

Provider cursor

The provider cursor belongs to the underlying provider. It may be int, MatrixCursor, For2Cursor, or another cursor type.

RefMatrix itself works in MatrixCursor space. The projection is responsible for converting from MatrixCursor to the provider cursor.


Core types

MatrixCursor

MatrixCursor represents a matrix cell position.

public struct MatrixCursor
{
    public required int Row;
    public required int Column;
}

The constructor uses row-first order:

var cursor = new MatrixCursor(row: 1, column: 2);

MatrixBounds

MatrixBounds describes local matrix bounds.

public readonly struct MatrixBounds : ICursorBoundsSupport<MatrixCursor>
{
    public int RowCount { get; }
    public int ColumnCount { get; }
}

It exposes local bounds as half-open cursor range:

start        = Row 0,        Column 0
endExclusive = Row RowCount, Column ColumnCount

MatrixBounds allows zero row count or zero column count. This is important for empty submatrix views.


MatrixDriver

MatrixDriver enumerates a rectangular matrix cursor range in row-major order.

(row 0, column 0)
(row 0, column 1)
(row 0, column 2)
(row 1, column 0)
...

It works with half-open bounds:

start         // inclusive
endExclusive  // exclusive

Empty column ranges or empty row ranges produce no elements.


MatrixToFor2CursorProjection

Maps matrix coordinates to generic two-level For2Cursor coordinates:

MatrixCursor.Row    -> For2Cursor.Outer
MatrixCursor.Column -> For2Cursor.Inner

This allows matrix code to reuse generic For2 infrastructure.


MatrixToLinearProjection

Maps MatrixCursor to a linear int cursor.

It is used by array/span-backed matrices.

Conceptually:

absoluteRow    = localRow + rowOffset
absoluteColumn = localColumn + columnOffset
linearCursor   = absoluteRow * totalColumnCount + absoluteColumn

This projection is reversible as long as projected cursors belong to the projected matrix region.


MatrixOffsetProjection

Maps a local matrix cursor to a parent matrix cursor by adding row and column offsets.

It is used for submatrices and block views.

local row/column -> parent row/column

Nested submatrices are implemented by composing bounded sources and offset projections.


Source/provider interfaces

The matrix model uses providers and value providers rather than direct storage ownership.

IRefProvider<T, TCursor>

Provides writable ref access to an item by cursor.

public interface IRefProvider<T, TCursor>
    where T : struct
    where TCursor : struct, allows ref struct
{
    ref T GetReference(scoped in TCursor cursor);
}

IValueProvider<T, TCursor>

Provides value-based access by cursor.

public interface IValueProvider<out T, TCursor>
    where T : struct, allows ref struct
    where TCursor : struct, allows ref struct
{
    T GetValue(scoped in TCursor cursor);
}

scoped in is important because returned values may be ref struct views. The cursor must not escape through the returned value.


ICursorBoundsSupport<TCursor>

Exposes cursor bounds.

public interface ICursorBoundsSupport<TCursor>
{
    void GetCursorBounds(out TCursor start, out TCursor endExclusive);
}

ICounterSupport

Exposes item count.

public interface ICounterSupport
{
    int Count { get; }
}

RefMatrix type shape

The core matrix type is projection-based:

public ref struct RefMatrix<T, TProvider, TProviderCursor, TProjection>
    where T : struct
    where TProvider : struct, IRefProvider<T, TProviderCursor>, allows ref struct
    where TProviderCursor : struct, allows ref struct
    where TProjection : struct,
        IReversibleCursorProjection<MatrixCursor, TProviderCursor>,
        allows ref struct

Internally it stores:

private RefBoundedSource<
    T,
    MatrixCursor,
    TProvider,
    TProviderCursor,
    TProjection,
    MatrixBounds> _source;

The matrix itself always works in local MatrixCursor space.

The provider remains in its own cursor space.

The projection connects the two.


Creating matrices

The recommended creation API is via extension/factory methods.

Span-backed matrix

Span<int> data = stackalloc int[6]
{
    1, 2, 3,
    4, 5, 6
};

var matrix = data.AsRefMatrix(
    totalColumnCount: 3,
    totalRowCount: 2);

matrix[1, 1] = 42;

The backing span now contains:

1, 2, 3, 4, 42, 6

Array-backed matrix

var data = new[]
{
    1, 2, 3,
    4, 5, 6
};

var matrix = data.AsRefMatrix(3, 2);

matrix[1, 2] = 99;

The backing array now contains:

1, 2, 3, 4, 5, 99

Windowed matrix over larger storage

var data = Enumerable.Range(0, 80).ToArray();

var matrix = data.AsRefMatrix(
    totalColumnCount: 10,
    totalRowCount: 8,
    columnRange: 2..6,
    rowRange: 2..6);

The window has:

ColumnCount = 4
RowCount    = 4
Count       = 16

Local coordinates map through MatrixToLinearProjection to the original backing storage.

Example:

local [0,0] -> absolute row 2, column 2 -> storage[22]
local [1,1] -> absolute row 3, column 3 -> storage[33]

Indexing

RefMatrix supports two main indexers.

Cell indexer

ref T this[int row, int column]

Example:

matrix[0, 0] = 10;
matrix[0, 1] = 11;
matrix[0, 2] = 12;
matrix[1, 0] = 13;

The indexer validates local matrix bounds. For a 3 x 2 matrix:

valid rows:    0..1
valid columns: 0..2

This prevents accidental row wraparound, such as matrix[0, 3] reading the first element of the next row.

Linear indexer

ref T this[Index index]

The linear indexer uses row-major order.

ref var first = ref matrix[Index.FromStart(0)];
ref var last = ref matrix[^1];

Enumeration

RefMatrix enumerates cells by writable reference in row-major order.

foreach (ref var cell in matrix)
{
    cell += 1;
}

For a 3 x 2 matrix, enumeration order is:

[0,0], [0,1], [0,2], [1,0], [1,1], [1,2]

The explicit enumerable form is also available:

var enumerable = matrix.AsRefEnumerable();
var enumerator = enumerable.GetEnumerator();

Submatrices

Submatrices are views over the same backing storage.

var sub = matrix.GetSubMatrix(
    rowOffset: 1,
    columnOffset: 1,
    rowCount: 2,
    columnCount: 3);

Submatrix coordinates are local to the submatrix and start at [0,0].

sub[0, 0] = 100;

The original matrix sees the same change.

Nested submatrices are supported:

var sub = matrix.GetSubMatrix(1, 1, 2, 3);
var nested = sub.GetSubMatrix(1, 1, 1, 2);

Empty submatrix views are allowed:

var emptyColumns = matrix.GetSubMatrix(
    rowOffset: 1,
    columnOffset: 2,
    rowCount: 2,
    columnCount: 0);

var emptyRows = matrix.GetSubMatrix(
    rowOffset: 2,
    columnOffset: 1,
    rowCount: 0,
    columnCount: 3);

Enumeration over an empty submatrix produces no elements.


Rows and columns

Rows and columns are exposed as matrix blocks.

var rows = matrix.Rows;
var columns = matrix.Columns;

For a 3 x 2 matrix:

1 2 3
4 5 6

Rows:

var rowIndex = 0;
var row0 = rows.GetValue(in rowIndex);

row0 has:

RowCount    = 1
ColumnCount = 3

Columns:

var columnIndex = 1;
var column1 = columns.GetValue(in columnIndex);

column1 has:

RowCount    = 2
ColumnCount = 1

Rows and columns are writable views over the original storage.


Blocks

A matrix can be divided into equally sized blocks.

var blocks = matrix.GetBlocks(
    blockColumnCount: 3,
    blockRowCount: 3);

For a 9 x 9 matrix divided into 3 x 3 blocks, block indexes are row-major:

0 1 2
3 4 5
6 7 8

Each block is returned as a writable RefMatrix<...> view.

var centerBlockIndex = 4;
var centerBlock = blocks.GetValue(in centerBlockIndex);

centerBlock[1, 1] = 777;

The original matrix and backing storage see the change.

Block sizes must be positive and must divide the current matrix dimensions exactly.

Enumerating blocks

RefMatrixBlocks is a value-based block view. Blocks are returned by value because each block is itself a matrix view.

var blocks = matrix.GetBlocks(3, 3);

var enumerator = blocks.GetEnumerator();
while (enumerator.MoveNext())
{
    var block = enumerator.Current;
    block[0, 0] = 1;
}

AsValueEnumerable() is also available:

var enumerable = blocks.AsValueEnumerable();

AsEnumerable() may be used as a readability alias when enabled.


Copying data out

CopyTo(Span<T>)

Copies matrix content into a target span in row-major order.

Span<int> target = stackalloc int[matrix.Count];
matrix.CopyTo(target);

The method accepts scoped Span<T> and does not store the span.

CopyTo(T[] data, int arrayIndex = 0)

Copies matrix content into an array starting at the given index.

var target = new int[10];
matrix.CopyTo(target, arrayIndex: 2);

This is a convenience extension over CopyTo(Span<T>).

ToArray()

Creates a new one-dimensional array in row-major order.

var values = matrix.ToArray();

To2DArray()

Creates a new two-dimensional array with dimensions [row, column].

var values = matrix.To2DArray();
var value = values[row, column];

Copying data in

CopyRawFrom(ReadOnlySpan<T>)

Copies raw span values into the matrix in row-major order.

ReadOnlySpan<int> values = stackalloc[] { 1, 2, 3, 4, 5, 6 };
matrix.CopyRawFrom(values);

CopyRawFrom(IEnumerable<T>)

Copies raw linear values into the matrix in row-major order.

matrix.CopyRawFrom(new[] { 1, 2, 3, 4, 5, 6 });

The source may contain more values than required. Only the first Count values are used.

If the source does not contain enough values, an exception is thrown.

CopyRawFrom(T[] data, int arrayIndex = 0)

Copies raw array values starting at the given index.

matrix.CopyRawFrom(data, arrayIndex: 2);

This is a convenience extension over CopyRawFrom(ReadOnlySpan<T>).

CopyFrom(ref RefMatrix<...> source)

Copies from another matrix view.

matrix.CopyFrom(ref otherMatrix);

Matrix-to-matrix copy requires the same dimensions:

RowCount and ColumnCount must match

This is intentionally stricter than raw copy. It preserves matrix semantics instead of merely copying the first Count values.


Filling

var value = 777;
matrix.Fill(in value);

Fill writes the same value to every visible cell in row-major enumeration order.

Calling Fill on an empty submatrix is valid and does not change the backing storage.


Comparing content

Use ContentEquals for content comparison.

Do not use Equals for matrix content comparison, because Equals normally suggests object/value identity semantics.

Compare with another matrix

var same = matrix.ContentEquals(ref otherMatrix);

With a custom comparer:

var same = matrix.ContentEquals(
    ref otherMatrix,
    static (ref readonly int a, ref readonly int b) => Math.Abs(a) == Math.Abs(b));

Matrix-to-matrix comparison checks dimensions first.

Compare with raw values

ReadOnlySpan<int> expected = stackalloc[] { 1, 2, 3, 4, 5, 6 };
var same = matrix.ContentEquals(expected);

The span length must match matrix.Count.


Formatting

Formatting helpers are exposed as operation extensions.

Default display string

var text = matrix.ToDisplayString();

Default output uses rows separated by newline and cells separated by spaces.

For a 3 x 2 matrix:

1 2 3
4 5 6

Formatting with a value selector

var text = matrix.ToDisplayString(static value => $"[{value}]", ", ");

Output:

[1], [2], [3]
[4], [5], [6]

Use ToDisplayString(...) instead of overriding ToString() for content rendering.


Empty matrix and empty submatrix behavior

Creation helpers require positive total matrix dimensions:

array.AsRefMatrix(columnCount, rowCount);

columnCount and rowCount must be greater than zero.

However, submatrices with zero columns or zero rows are valid:

var emptyColumns = matrix.GetSubMatrix(1, 2, 2, 0);
var emptyRows = matrix.GetSubMatrix(2, 1, 0, 3);

For empty submatrices:

  • enumeration produces no elements;
  • CopyTo into an empty span succeeds;
  • ToArray() returns an empty array;
  • To2DArray() preserves the matrix shape, such as [2, 0] or [0, 3];
  • Fill does not change storage.

Block creation requires positive block sizes and compatible non-zero dimensions.


Complex projection chains

The projection model supports layered views.

For example:

linear storage
-> row-major matrix
-> submatrix
-> column-major linear view
-> row-major matrix over that linear view

The final matrix still writes through to the original backing storage.

This is the main design goal of the projection-based architecture: each layer owns only its projection and bounds; none of the layers copy the data.


allows ref struct and lifetime model

The matrix model is designed to work with ref struct providers such as span-backed sources.

This means allows ref struct must be carried through every generic layer that may accept or store a ref struct type argument.

For example:

where TProvider : struct, IRefProvider<T, TProviderCursor>, allows ref struct

If a type stores TProvider and TProvider may be a ref struct, then the containing type must also be stack-only compatible.

Prefer by-value constructor parameters for stored providers:

public RefMatrix(TProvider provider, TProjection projection, MatrixBounds bounds)

Avoid storing values received through in TProvider when TProvider may be a ref struct. A parameter passed by in is a borrowed readonly reference, and storing it into a field can violate ref-safety rules.

Use scoped for borrowed parameters that must not escape:

ref T GetReference(scoped in MatrixCursor cursor);
T GetValue(scoped in int cursor);
public void CopyTo(scoped Span<T> data);
public void CopyRawFrom(scoped ReadOnlySpan<T> values);

Design rules

Keep matrix views as views

RefMatrix does not own data.

It is a view over a provider.

Creating a submatrix, row, column, or block does not copy data.


Keep raw copy and matrix copy separate

Use raw-copy methods for linear input:

CopyRawFrom(ReadOnlySpan<T>)
CopyRawFrom(IEnumerable<T>)
CopyRawFrom(T[] data, int arrayIndex = 0)

Use matrix-copy methods for matrix-aware input:

CopyFrom(ref RefMatrix<...> matrix)

Avoid adding CopyFrom(T[] data, ...), because arrays are raw linear input, not matrix sources.


Prefer ContentEquals over Equals

Matrix content comparison should be explicit.

Use:

ContentEquals(...)

Avoid adding Equals(...) methods for content comparison.


Keep source-level enumeration in sources

Value enumeration belongs at source/provider level.

For example, RefMatrixBlocksSource builds ValueEnumerable over a block provider. RefMatrixBlocks is only a facade over that source.


Tested behavior

The current test set covers:

  • MatrixBounds cursor bounds;
  • MatrixDriver row-major enumeration;
  • MatrixOffsetProjection round-tripping;
  • MatrixToFor2CursorProjection row/column mapping;
  • MatrixToLinearProjection row-major mapping with offsets;
  • AsRefMatrix(...) creation and range validation;
  • matrix indexer validation;
  • matrix enumeration by writable reference;
  • AsRefEnumerable();
  • GetSubMatrix(...) and nested submatrices;
  • empty submatrix behavior;
  • writable views into backing storage;
  • rows, columns, and blocks;
  • block enumeration through ValueEnumerable;
  • writable block views;
  • copy-in and copy-out helpers;
  • Fill;
  • ToArray() and To2DArray();
  • content comparison;
  • display formatting;
  • complex projection chains.

Several tests intentionally protect against subtle bugs that were found during development:

  • MatrixDriver must start at the correct first cursor;
  • empty ranges must not enumerate fake rows or columns;
  • Rows and Columns must not be swapped;
  • matrix[row, column] must not allow row wraparound through linear projection;
  • nested views must preserve backing storage mapping;
  • blocks must be indexed in row-major order;
  • projections must preserve row/column semantics;
  • complex projection chains must write through to the original storage.

Example

var data = new[]
{
    1, 2, 3,
    4, 5, 6,
    7, 8, 9
};

var matrix = data.AsRefMatrix(3, 3);

matrix[1, 1] = 42;

var row = matrix.GetRow(1);
var column = matrix.GetColumn(1);

var blockIndex = 4;
var block = matrix.GetBlocks(1, 1).GetValue(in blockIndex);

var copy = matrix.ToArray();

var text = matrix.ToDisplayString();

Expected matrix content:

1 2 3
4 42 6
7 8 9

Notes

This API is optimized for explicit, source-based, allocation-conscious matrix operations.

The design intentionally favors:

  • explicit provider boundaries;
  • stack-only compatibility;
  • writable ref access;
  • predictable row-major traversal;
  • clear separation between local and provider coordinate spaces;
  • composable cursor projections;
  • no hidden data copies for views.
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.7 121 5/28/2026
2.0.6 103 5/27/2026

Separate RefMatrix to independent packages