Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why don't number types share a common interface?

Tags:

c#

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);
}
like image 525
Jens Avatar asked Dec 01 '10 08:12

Jens


4 Answers

Update

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 and EqualityComparer<T>.Default singletons implement IComparer<T> and IEqualityComparer<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;
}
like image 200
Dirk Vollmar Avatar answered Nov 01 '22 23:11

Dirk Vollmar


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.

like image 36
Saeed Amiri Avatar answered Nov 01 '22 21:11

Saeed Amiri


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.

like image 3
Dan Avatar answered Nov 01 '22 22:11

Dan


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>
like image 2
Marc Gravell Avatar answered Nov 01 '22 22:11

Marc Gravell