How Numerics.NET supports C# features

With the release of .NET 9 also comes a new version of C#. As the language evolves, new avenues open up for developers to write faster code more quickly. Improvements in the language often go hand in hand with improvements in the runtime.

Many of the improvements happen below the surface, where new language features just produce more efficient code. The pervasive use of spans to avoid copying data is one example.

At other times, the changes are visible in the API. Again, spans are a great example. For version 9, we added dozens of new API’s that take spans as arguments.

New language features often require some API support. This is what this post is about. I will highlight some of the ways in which Numerics.NET enables you to take advantage of the increasing expressiveness and power of the C# language.

Collection expressions

Collection expressions were introduced in C# 12. They provide a concise way of initializing collections, often in a way that avoids duplication or copying:

C#
Span<double> values = [ 1, 2, 3, 4, 5];

One interesting aspect of collection expressions is that they are target typed. Target typing means that the type of the expression is inferred from the context in which it is used.

The concept of target typing is not new to C#. It was introduced in C# 9 with target-typed new expressions. You can omit the type of the object being created when the compiler is able to infer it from the context.

A collection expression can be converted to many different types of collections, including many common .NET types like arrays and lists. Certain conditions must be met for the C# compiler to consider a collection type a valid target type.

With the latest update, both Numerics.NET vectors and matrices meet these conditions. This means that you can now initialize vectors and matrices using collection expressions:

C#
using Numerics.NET;

Vector<double> v = [ 1, 2, 3, 4, 5 ];
Matrix<double> m = [ [1, 2], [3, 4], [5, 6] ];

You can even use spread elements to join vectors or mix and match values and vectors:

C#
Vector<double> v1 = [ 1, 2, 3 ];
Vector<double> v2 = [ 4, 5, 6 ];
Vector<double> v3 = [ ..v1, 99, ..v2 ];
// v3 = [ 1, 2, 3, 99, 4, 5, 6 ];

2-Tuples as Complex Numbers

C# 7 introduced value tuples, which are tuples that are value types. Value tuples are a great way to return multiple values from a method. C# has a concise syntax for tuples, which makes them ideal for initializing and returning multiple values.

In Numerics.NET, the type that represents complex numbers, Complex<T>, has an implicit conversion from a tuple of two values. In many situations where you need to create a complex number, you can now use a tuple instead of the more verbose constructor. This makes it possible to work with complex numbers in a more natural way:

C#
using Numerics.NET;

Complex<double> c = (1, 2);
// c = 1 + 2i

The reverse is also possible. You can deconstruct a complex number into a tuple consisting of the real and imaginary parts of the number:

C#
Complex<double> c = (1, 2);
var (a, b) = c;
// a = 1, b = 2

Deconstruction

In general, deconstruction is the process of turning an object or struct into its component parts. The feature was introduced in C# 7.

Besides complex numbers, there are many other types in Numerics.NET that can be deconstructed. For example, rational numbers can be deconstructed into a tuple of numerator and denominator.

One of the most useful applications of deconstruction is in matrix decompositions. A matrix decomposition is a way of expressing a matrix as a product of matrices with specific properties. For example, the LU decomposition expresses a matrix as the product of a lower triangular matrix (usually with ones on the diagonal) and an upper triangular matrix.

All matrix decomposition classes in Numerics.NET have deconstructors that return a tuple of the factors. For example, the singular value decomposition of a matrix, which expresses a matrix as the product of three matrices, can be deconstructed like this:

C#
using Numerics.NET;

var A = Matrix.CreateRandom(5, 3);
var (U, Σ, V) = A.GetSingularValueDecomposition();

Using deconstruction, you get the three factors of the singular value decomposition directly, without having to call separate properties to get each factor.

Conclusion

Computer science is a field that is constantly evolving. Programming languages are no exception. New features are added to languages to make them more expressive, more efficient, and easier to use.

We continuously update Numerics.NET to take advantage of the latest features in C# and the .NET runtime. This makes it easier for developers to write high-performance code without having to worry about the underlying implementation details.