Reputation: 25563
I recently ran across the problem, that I wanted a function to work on both doubles and integers and wondered, why there is no common interface for all number types (containing arithmetic operators and comparisons).
It would make writing functions like Math.Min
(which exist in a gazillion overloads) way more convenient.
Would introducing an additional interface be a breaking change?
Edit: I think about using this like
public T Add<T>(T a, T b) where T: INumber
{
return a+b;
}
or
public T Range<T>(T x, T min, T max) where T:INumber
{
return Max(x, Min(x, max), min);
}
Upvotes: 34
Views: 12203
Reputation: 2463
As of .NET 7 this feature seems more than satisfied.
Build-in numeric types in .NET now do support any basic numeric, arithmetic and maths operation including some more advanced, through interfaces.
Moreover, some of these interface expose operator overloading. So you can use "natural syntax" for e.g. addition using the "+"-op and not a method call like "Add()".
System.Int32 for example in .NET 7 implements as many as 21 numerical interfaces, which are the following (see below). So maths can be done now using generics. I do not know how the runtime experience is, just read the docs ...
public readonly struct Int32 : IComparable, IConvertible, IEquatable, IParsable, ISpanParsable, System.Numerics.IAdditionOperators<int,int,int>, System.Numerics.IAdditiveIdentity<int,int>, System.Numerics.IBinaryInteger, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators<int,int,int>, System.Numerics.IComparisonOperators<int,int,bool>, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators<int,int,int>, System.Numerics.IEqualityOperators<int,int,bool>, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators<int,int,int>, System.Numerics.IMultiplicativeIdentity<int,int>, System.Numerics.IMultiplyOperators<int,int,int>, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IShiftOperators<int,int,int>, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators<int,int,int>, System.Numerics.IUnaryNegationOperators<int,int>, System.Numerics.IUnaryPlusOperators<int,int>
https://learn.microsoft.com/de-de/dotnet/api/system.numerics?view=net-7.0#interfaces
https://learn.microsoft.com/en-us/dotnet/api/system.int32?view=net-7.0
Upvotes: 0
Reputation: 563
Finally, starting from .NET 7 you can use INumber<TSelf> Interface under namespace System.Numerics
:
static T Add<T>(T left, T right) where T : INumber<T>
{
return left + right;
}
Upvotes: 3
Reputation: 22555
How to compute 2 + 2.35? Is it 4 or 4.35 or 4.349999? How interface can find the appropriate output type? You may write your own extension method and use overloading to solve a specific problem, but if we want to have an interface that handles all edge cases, then just imagine how long will be the size of the interface and as a consequence finding a useful function is practically impossible. Additionally, the interface adds overheads and numbers are usually the basis of calculation so we can't rely on slow operations.
Here is how you can write your own extension class:
public static class ExtensionNumbers
{
public static T Range<T>(this T input, T min, T max) where T : class
{
return input.Max(input.Min(max), min);
}
public static T Min<T>(this T input, params T[] param) where T : class
{
return null;
}
private static T Max<T>(this T input, params T[] number) where T : class
{
return null;
}
}
I used where T : class
just to be able to compile it.
Upvotes: 4
Reputation: 176179
Generic math is coming as a preview feature to .NET 6. Read the announcement in the .NET dev blog:
https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/
If you want to do such kind of "generic" arithmetics your option in a strongly-typed language such as C# are quite limited. Marc Gravell described the problem as follows:
.NET 2.0 introduced generics into the .NET world, which opened the door for many elegant solutions to existing problems. Generic constraints can be used to restrict the type-arguments to known interfaces etc, to ensure access to functionality - or for simple equality/inequality tests the
Comparer<T>.Default
andEqualityComparer<T>.Default
singletons implementIComparer<T>
andIEqualityComparer<T>
respectively (allowing us to sort elements for instance, without having to know anything about the "T" in question).With all this, though, there is still a big gap when it comes to operators. Because operators are declared as static methods, there is no
IMath<T>
or similar equivalent interface that all the numeric types implement; and indeed, the flexibility of operators would make this very hard to do in a meaningful way. Worse: many of the operators on primitive types don't even exist as operators; instead there are direct IL methods. To make the situation even more complex, Nullable<> demands the concept of "lifted operators", where the inner "T" describes the operators applicable to the nullable type - but this is implemented as a language feature, and is not provided by the runtime (making reflection even more fun).
However, C# 4.0 introduced the dynamic
keyword which you can use to choose the correct overload at runtime:
using System;
public class Program
{
static dynamic Min(dynamic a, dynamic b)
{
return Math.Min(a, b);
}
static void Main(string[] args)
{
int i = Min(3, 4);
double d = Min(3.0, 4.0);
}
}
You should be aware that this removes type-safety and you might get exceptions at runtime if the dynamic runtime cannot find a suitable overload to call, e.g. because you mixed types.
If you want to get type-safety you might want to have a look at the classes available in the MiscUtil library providing generic operators for basic operations.
Please note that if you are only after specific operations you actually might use the interfaces that the built-in types already implement. For example, a type-safe generic Min
function could look like this:
public static T Min<T>(params T[] values) where T : IComparable<T>
{
T min = values[0];
foreach (var item in values.Skip(1))
{
if (item.CompareTo(min) < 0)
min = item;
}
return min;
}
Upvotes: 16
Reputation: 7724
They soon will support a common interface!
Check out this .NET Blog post.
Starting in .NET 6 (preview 7 I think), you will be able to make use of interfaces such as INumber
and IFloatingPoint
to create programs such as:
using System;
Console.WriteLine(Sum(1, 2, 3, 4, 5));
Console.WriteLine(Sum(10.541, 2.645));
Console.WriteLine(Sum(1.55f, 5, 9.41f, 7));
static T Sum<T>(params T[] numbers) where T : INumber<T>
{
T result = T.Zero;
foreach (T item in numbers)
{
result += item;
}
return result;
}
INumber
currently comes from the System.Runtime.Experimental
NuGet package. My project file for the sample above looks like
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EnablePreviewFeatures>true</EnablePreviewFeatures>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Runtime.Experimental" Version="6.0.0-preview.7.21377.19" />
</ItemGroup>
</Project>
There are also interfaces such as IAdditionOperators
and IComparisonOperators
so you can make use of specific operators generically.
Upvotes: 3
Reputation: 1062945
It isn't as simple as introducing an interface, as the operators available are different per type, and are not always even homogeneous (i.e. DateTime + TimeSpan => DateTime, DateTime - DateTime => TimeSpan).
At the techincal level, there may be lot of boxing etc involved here, plus regular operators are static
.
Actually, for Min
/ Max
, Comparer<T>.Default.Compare(x,y)
does pretty much everything you would hope.
For other operators: in .NET 4.0 dynamic
is of great help here:
T x = ..., y = ...;
T sum = (dynamic)x + (dynamic)y;
but otherwise "MiscUtil" has generic support for operators via the Operator
class, i.e.
T x = ..., y = ...;
T sum = Operator.Add(x, y); // actually Add<T>
Upvotes: 2
Reputation: 117250
Going with Matthew's answer, do notice the difference between the 3 invocations.
void DoSomething(ref MyStruct something)
{
something.DoSomething();
}
static void Main(string[] args)
{
var s = new MyStruct(10);
var i = (IDoSomething)s;
DoSomething(s); // will not modify s
DoSomething(i); // will modify i
DoSomething(ref s); // will modify s, but with no reassignment
}
Upvotes: 0
Reputation: 25505
The problem is that in the architecture of how numbers are stored different number types are treated fundimentaly differently. For starters your verbiage is wrong and interfaces won't work, but what I think your getting at is you want numerics to be loosely typed.
Just for a start as to why you wouldn't want to do this consider Integer types are a one to one mapping on the range of values they can represent while floating point types have a persision and and exponent component since there are infantly many floating point numbers. The language developers would have to make some very fundamental and potentially error causing assumptions in the language design.
Take a look at this article about floating point math for more info.
Upvotes: 0
Reputation: 61599
Well, you can't define operators in interfaces anyway and structs (although they support interfaces) wouldn't work well through interface implementations, as this would require boxing and unboxing, which of course would be a big performance hit when performing mathmatical operations purely through interface implementations.
I would also highlight that when you cast a struct to its interface, the result object is a reference type (a boxed object) which you perform operations on, not the original struct itself:
interface IDoSomething
{
void DoSomething();
}
struct MyStruct : IDoSomething
{
public MyStruct(int age)
{
this.Age = age;
}
public int Age;
pubblic void DoSomething()
{
Age++;
}
}
public void DoSomething(IDoSomething something)
{
something.DoSomething();
}
When I pass in my instance of my struct, its boxed (becomes a reference type) which I perform my DoSomething
operation on, but my original instance of my struct will not change.
Upvotes: 1
Reputation: 7634
That's the main characteristic of a "strongly-typed language". This is something that avoids billions of mistakes a minute. Of course we want int to be totally different as double.
Upvotes: -4