| name | csharp-generic-math |
| description | Use C# generic math (System.Numerics interfaces, static abstract members) to write type-parameter-agnostic arithmetic and mathematical algorithms in .NET 7+. Trigger whenever the user writes or reviews generic methods that do math, asks about INumber, IAdditionOperators, IFloatingPoint, or other System.Numerics interfaces, wants to support multiple numeric types without overloads, is implementing custom numeric/vector types, or uses Math/MathF — even if they don't mention "generic math" by name. Always prefer T.Zero/T.One and type-level math over System.Math when a type parameter is involved. |
C# Generic Math Skill
.NET 7 + C# 11 introduced static abstract/virtual interface members in System.Numerics, letting you write a single generic method or type that works for all numeric types — int, float, double, Half, decimal, custom vectors, and more — without writing per-type overloads.
Why this matters
Before .NET 7, arithmetic operators couldn't appear in interface contracts, so libraries had to either duplicate code for every numeric type or fall back to slow reflection/boxing. Generic math eliminates both problems: the compiler resolves every T.Zero or + call at build time, producing the same code as a hand-written overload.
Key wins:
- Single generic implementation replaces N per-type overloads.
- Compile-time resolution — zero boxing, zero runtime dispatch.
- Reaches custom types — any type that implements the right interfaces works automatically.
- Type-safe constants —
T.Zero, T.One, T.Pi instead of magic literals.
- Deprecates
System.Math/System.MathF — call float.Sin(x) or T.Sqrt(x) instead.
Quick reference
| Topic | See |
|---|
Choosing the right constraint (INumber<T> vs narrower interfaces) | constraints.md |
| All numeric, operator, function, and parsing interfaces | interfaces.md |
| Implementing generic math on custom types | custom-types.md |
| Common mistakes, pitfalls, and analyzer hints | pitfalls.md |
Core constraints at a glance
where T : INumber<T>
where T : IFloatingPoint<T>
where T : IFloatingPointIeee754<T>
where T : IBinaryInteger<T>
where T : IAdditionOperators<T,T,T>
Prefer the narrowest constraint that satisfies the algorithm. INumber<T> is convenient but rejects complex-number types; specific operator interfaces are more inclusive.
Essential patterns
Generic sum
static T Sum<T>(IEnumerable<T> source)
where T : IAdditiveIdentity<T, T>, IAdditionOperators<T, T, T>
{
var sum = T.AdditiveIdentity;
foreach (var value in source)
sum += value;
return sum;
}
Generic average
static TResult Average<T, TResult>(T first, T second)
where T : INumber<T>
where TResult : INumber<TResult>
{
return TResult.CreateChecked((first + second) / T.CreateChecked(2));
}
Type-specific math (no System.Math)
var sinFloat = float.Sin(float.Pi);
var sinDouble = double.Sin(double.Pi);
static T UnitCircleY<T>(T angle)
where T : ITrigonometricFunctions<T>
=> T.Sin(angle);
Creating numeric values safely
| Method | Behaviour when value doesn't fit |
|---|
T.CreateChecked(value) | throws OverflowException |
T.CreateSaturating(value) | clamps to T.MinValue/T.MaxValue |
T.CreateTruncating(value) | wraps (bit-truncation) |
Useful static members on numeric types
T.Zero
T.One
T.AdditiveIdentity
T.MultiplicativeIdentity
T.MinValue / T.MaxValue
T.Pi / T.E / T.Tau
T.IsNaN(value)
T.IsInfinity(value)
T.IsInteger(value)
What to read next