Monkeymoto.INumberAlias 1.0.0

dotnet add package Monkeymoto.INumberAlias --version 1.0.0
                    
NuGet\Install-Package Monkeymoto.INumberAlias -Version 1.0.0
                    
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="Monkeymoto.INumberAlias" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Monkeymoto.INumberAlias" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Monkeymoto.INumberAlias" />
                    
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 Monkeymoto.INumberAlias --version 1.0.0
                    
#r "nuget: Monkeymoto.INumberAlias, 1.0.0"
                    
#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 Monkeymoto.INumberAlias@1.0.0
                    
#: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=Monkeymoto.INumberAlias&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Monkeymoto.INumberAlias&version=1.0.0
                    
Install as a Cake Tool

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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