Monkeymoto.INumberAlias
1.0.0
dotnet add package Monkeymoto.INumberAlias --version 1.0.0
NuGet\Install-Package Monkeymoto.INumberAlias -Version 1.0.0
<PackageReference Include="Monkeymoto.INumberAlias" Version="1.0.0" />
<PackageVersion Include="Monkeymoto.INumberAlias" Version="1.0.0" />
<PackageReference Include="Monkeymoto.INumberAlias" />
paket add Monkeymoto.INumberAlias --version 1.0.0
#r "nuget: Monkeymoto.INumberAlias, 1.0.0"
#:package Monkeymoto.INumberAlias@1.0.0
#addin nuget:?package=Monkeymoto.INumberAlias&version=1.0.0
#tool nuget:?package=Monkeymoto.INumberAlias&version=1.0.0
INumberAlias
Monkeymoto.INumberAlias
C# class library which provides primitive type aliasing for types using generic math interfaces.
The typedef problem
Across StackOverflow,
Reddit,
and GitHub, developers
have sought solutions for typedef-style
type aliasing in C# for decades.
Global using statements
Since C# 10 (.NET 6), it has been possible to come close to this with a
global using statement:
#if REAL_IS_DOUBLE
global using real = System.Double;
#else
global using real = System.Single;
#endif
This example would provide a real type across the assembly, as an alias for
either double or float. However, this approach does not allow developers to
provide type aliases that are shared between multiple assemblies. For library
authors, there is no built-in language support to create cross-assembly type
aliases.
Implicit conversion operators
Another approach would be to simply use implicit conversion operators. This would work for many scenarios out of the box. With implicit conversions to and from the aliased type, existing operators (built-in and user-defined) would work on the alias type with only these conversion operators needing to be defined.
For example, mathematical operators like + and *= would work on a type for
which there are implicit conversions defined to and from int. However, there
is a gap in coverage with this approach: generic methods would not be able to
access these operators.
Static abstract members in interfaces
.NET 7 introduced the
static abstract members in interfaces
feature, and with it, the ability for operators to be defined for interface
declarations. The generic math interfaces, like
INumber<TSelf>,
provide a common surface area for generic methods, and particularly library
authors to leverage when working with a variety of types representing numbers.
These generic math interfaces give us a way to share that surface area for our alias types with generic methods which consume them, closing that gap.
Our approach
The generic math interfaces give us access to a lot of functionality, but with great power comes great boilerplate, and there's a lot. Like, a LOT. That is the purpose of this library, to reduce the boilerplate.
We can't reduce our type aliasing to a simple typedef statement, but we can
achieve our purposes with relatively little effort:
public readonly struct WindowID(uint value = default) :
IBinaryNumberAlias<WindowID, uint>,
IUnsignedNumberAlias<WindowID, uint>
{
public readonly uint Value = value;
static WindowID INumberBaseAlias<WindowID, uint>.FromValue(uint value) => new(value);
static uint INumberBaseAlias<WindowID, uint>.ToValue(WindowID value) => value.Value;
}
Here, we define a type alias for uint, named WindowID. The struct has one
field, to store the underlying value. The FromValue and ToValue static
methods are necessary for the provided interfaces to map values between the
alias type and the underlying type.
In this example, WindowID fully implements the
System.Numerics.IBinaryNumber<WindowID> and
System.Numerics.IUnsignedNumber<WindowID> interfaces. Generic methods
consuming WindowID can use the full range of operators and properties that
are accessible to uint, without you needing to add any additional code.
Weak vs. strong type aliasing
If we had implemented implicit conversion operators for WindowID, then the
type would be nearly interchangeable with uint (except when an object's
Type is directly involved, of course). Mathematical operators and generic
math interfaces would now work the same for both types, for example. This is
a good analogue of a C++ typedef, with our type aliases being weakly
interchangeable with the underlying type. This is weak type aliasing.
However, what if you don't want your type alias to be implicitly convertible
back to the underlying type, or even implicitly convertible from that type? If
we define the conversion operators for WindowID as explicit, then we lose
access to all of the operators (mathematical, equality, and comparison) that
the implicit conversions gave us access to (when using the alias type directly;
the generic math interfaces would still work).
The final piece to our approach is to provide base classes that will still define the operators that the generic math interfaces without requiring implicit conversion back to the underlying type. This is strong type aliasing.
Alias base classes
The Monkeymoto.INumberAlias.AliasBase.NumberBaseAliasBase<TSelf, TValue>,
Monkeymoto.INumberAlias.AliasBase.NumberAliasBase<TSelf, TValue>,
Monkeymoto.INumberAlias.AliasBase.BinaryNumberAliasBase<TSelf, TValue>,
Monkeymoto.INumberAlias.AliasBase.BinaryIntegerAliasBase<TSelf, TValue>, and
Monkeymoto.INumberAlias.AliasBase.BinaryFloatingPointIeee754AliasBase<TSelf, TValue>
abstract classes all serve as base classes for types that implement the
INumberBase<TSelf>,
INumber<TSelf>,
IBinaryNumber<TSelf>,
IBinaryInteger<TSelf>,
and
IBinaryFloatingPointIeee754<TSelf>
generic math interfaces, respectively.
Each of these base classes follow the inheritance hierarchy of the generic math
interfaces (e.g., BinaryIntegerAliasBase<TSelf, TValue> inherits from
BinaryNumberAliasBase<TSelf, TValue>, etc.), and define a constructor which
takes one parameter of type TValue. This constructor should be used to
produce the return value for the
INumberBaseAlias<TSelf, TValue>.FromValue(TValue) method.
The NumberBaseAliasBase<TSelf, TValue> base class defines a protected field
of type TSelf named value. This should be the value returned by the
INumberBaseAlias<TSelf, TValue>.ToValue(TSelf) method.
Using these base classes, we could now define our WindowID alias as:
public sealed class WindowID(uint value = default) :
BinaryNumberAliasBase<WindowID, uint>,
IBinaryNumberAlias<WindowID, uint>,
IUnsignedNumberAlias<WindowID, uint>
{
public static readonly WindowID Default = new();
static WindowID INumberBaseAlias<WindowID, uint>.FromValue(uint value) => new(value);
static uint INumberBaseAlias<WindowID, uint>.ToValue(WindowID value) => value?.value ?? default;
public static explicit operator WindowID(uint value) => new(value);
public static explicit operator uint(WindowID value) => value?.value ?? default;
}
With this, WindowID and uint will not be implicitly interchangeable, but
we will have access to the mathematical, equality, and conversion operators.
We provide the Default property here because default(WindowID) is null
(WindowID being a class type), and WindowID.Default is more readable than
new WindowID() to clarify our intentions.
Also note that in ToValue and the uint conversion operator, we null-check
our parameter. Remember that nullability annotations are just that,
annotations, and don't prevent someone doing something crazy like
(uint)(WindowID)null. Here, we treat such an expression as being explicitly
the same as (WindowID)default(uint).
This is, of course, much more verbose than a simple typedef statement, but
this enables us to define a strong type alias, with operators, implementations
of
IComparable,
IComparable<WindowID>,
IEquatable<WindowID>,
and the full range of interfaces that the generic math interfaces pull in.
Dependent type aliases
As a final note, you can also create type aliases that depend on another type
alias. The only type restriction for TValue is that it implements the
relevant generic math interface
(INumber<TSelf>
for INumberAlias<TSelf, TValue>/NumberAliasBase<TSelf, TValue>,
IBinaryInteger<TValue>
for IBinaryIntegerAlias<TSelf, TValue>/BinaryIntegerAliasBase<TSelf, TValue>,
etc.). So you could define a type alias with WindowID as its underlying
type.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
-
net9.0
- No dependencies.
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 |
|---|---|---|
| 1.0.0 | 94 | 2/16/2026 |