Preview Features in .NET 6 – Generic Math

Tanner

If you’ve ever wanted to use operators with generic types or thought that interfaces could be improved by supporting the ability to define static methods as part of their contract, then this blog post is for you. With .NET 6 we will be shipping a preview of the new generic math and static abstracts in interfaces features. These features are shipping in preview form so that we can get feedback from the community and build a more compelling feature overall. As such, they are not supported for use in a production environment in .NET 6. It is highly recommended that you try the feature out and provide feedback if there are scenarios or functionality you feel is missing or could otherwise be improved.

Requires preview features attribute

Central to everything else is the new RequiresPreviewFeatures attribute and corresponding analyzer. This attribute allows us to annotate new preview types and new preview members on existing types. With this capability, we can ship an unsupported preview feature inside a supported major release. The analyzer looks for types and members being consumed that have the RequiresPreviewFeatures attribute and will give a diagnostic if the consumer is not marked with RequiresPreviewFeatures itself. To provide flexibility in the scope of a preview feature, the attribute can be applied at the member, type, or assembly level.

Because preview features are not supported for use in production and the APIs will likely have breaking changes before becoming supported, you must opt-in to using them. The analyzer will produce build errors for any call sites that haven’t been opted-into preview feature usage. The analyzer is not available in .NET 6 Preview 7, but will be included in .NET 6 RC1.

Static Abstracts in Interfaces

C# is planning on introducing a new feature referred to as Static Abstracts in Interfaces. As the name indicates, this means you can now declare static abstract methods as part of an interface and implement them in the derived type. A simple but powerful example of this is in IParseable which is the counterpart to the existing IFormattable. Where IFormattable allows you to define a contract for generating a formatted string for a given type, IParseable allows you to define a contract for parsing a string to create a given type:

public interface IParseable<TSelf>
    where TSelf : IParseable<TSelf>
{
    static abstract TSelf Parse(string s, IFormatProvider? provider);

    static abstract bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out TSelf result);
}

public readonly struct Guid : IParseable<Guid>
{
    public static Guid Parse(string s, IFormatProvider? provider)
    {
        /* Implementation */
    }

    public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out Guid result)
    {
        /* Implementation */
    }
}

A quick overview of the feature is:

  • You can now declare interface members that are simultaneously static and abstract
  • These members do not currently support Default Interface Methods (DIMs) and so static and virtual is not a valid combination
  • This functionality is only available to interfaces, it is not available to other types such as abstract class
  • These members are not accessible via the interface, that is IParseable<Guid>.Parse(someString, null) will result in a compilation error

To elaborate on the last point, normally abstract or virtual members are invoked via some kind of virtual dispatch. For static methods we don’t have any object or instance in which to carry around the relevant state for true virtual dispatch and so the runtime wouldn’t be able to determine that IParseable<Guid>.Parse(...) should resolve to Guid.Parse. In order for this to work, we need to specify the actual type somewhere and that is achievable through generics:

public static T InvariantParse<T>(string s)
    where T : IParseable<T>
{
    return T.Parse(s, CultureInfo.InvariantCulture);
}

By using generics in the fashion above, the runtime is able to determine which Parse method should be resolved by looking it up on the concrete T that is used. If a user specified InvariantParse<int>(someString) it would resolve to the parse method on System.Int32, if they specified InvariantParse<Guid>(someString) it would resolve to that on System.Guid, and so on. This general pattern is sometimes referred to as the Curiously Recurring Template Pattern (CRTP) and is key to allowing the feature to work.

More details on the runtime changes made to support the feature can be found here.

Generic Math

One long requested feature in .NET is the ability to use operators on generic types. Using static abstracts in interfaces and the new interfaces being exposed in .NET, you can now write this code:

public static TResult Sum<T, TResult>(IEnumerable<T> values)
    where T : INumber<T>
    where TResult : INumber<TResult>
{
    TResult result = TResult.Zero;

    foreach (var value in values)
    {
        result += TResult.Create(value);
    }

    return result;
}

public static TResult Average<T, TResult>(IEnumerable<T> values)
    where T : INumber<T>
    where TResult : INumber<TResult>
{
    TResult sum = Sum<T, TResult>(values);
    return TResult.Create(sum) / TResult.Create(values.Count());
}

public static TResult StandardDeviation<T, TResult>(IEnumerable<T> values)
    where T : INumber<T>
    where TResult : IFloatingPoint<TResult>
{
    TResult standardDeviation = TResult.Zero;

    if (values.Any())
    {
        TResult average = Average<T, TResult>(values);
        TResult sum = Sum<TResult, TResult>(values.Select((value) => {
            var deviation = TResult.Create(value) - average;
            return deviation * deviation;
        }));
        standardDeviation = TResult.Sqrt(sum / TResult.Create(values.Count() - 1));
    }

    return standardDeviation;
}

This is made possible by exposing several new static abstract interfaces which correspond to the various operators available to the language and by providing a few other interfaces representing common functionality such as parsing or handling number, integer, and floating-point types. The interfaces were designed for extensibility and reusability and so typically represent single operators or properties. They explicitly do not pair operations such as multiplication and division since that is not correct for all types. For example, Matrix4x4 * Matrix4x4 is valid, Matrix4x4 / Matrix4x4 is not. Likewise, they typically allow the input and result types to differ in order to support scenarios such as double = TimeSpan / TimeSpan or Vector4 = Vector4 * float.

If you’re interested to learn more about the interfaces we’re exposing, take a look at the design document which goes into more detail about what is exposed.

Operator Interface Name Summary
IParseable Parse(string, IFormatProvider)
ISpanParseable Parse(ReadOnlySpan<char>, IFormatProvider)
IAdditionOperators x + y
IBitwiseOperators x & y, x | y, x ^ y, and ~x
IComparisonOperators x < y, x > y, x <= y, and x >= y
IDecrementOperators --x and x--
IDivisionOperators x / y
IEqualityOperators x == y and x != y
IIncrementOperators ++x and x++
IModulusOperators x % y
IMultiplyOperators x * y
IShiftOperators x << y and x >> y
ISubtractionOperators x - y
IUnaryNegationOperators -x
IUnaryPlusOperators +x
IAdditiveIdentity (x + T.AdditiveIdentity) == x
IMinMaxValue T.MinValue and T.MaxValue
IMultiplicativeIdentity (x * T.MultiplicativeIdentity) == x
IBinaryFloatingPoint Members common to binary floating-point types
IBinaryInteger Members common to binary integer types
IBinaryNumber Members common to binary number types
IFloatingPoint Members common to floating-point types
INumber Members common to number types
ISignedNumber Members common to signed number types
IUnsignedNumber Members common to unsigned number types

The binary floating-point types are System.Double (double), System.Half, and System.Single (float). The binary-integer types are System.Byte (byte), System.Int16 (short), System.Int32 (int), System.Int64 (long), System.IntPtr (nint), System.SByte (sbyte), System.UInt16 (ushort), System.UInt32 (uint), System.UInt64 (ulong), and System.UIntPtr (nuint). Several of the above interfaces are also implemented by various other types including System.Char, System.DateOnly, System.DateTime, System.DateTimeOffset, System.Decimal, System.Guid, System.TimeOnly, and System.TimeSpan.

Since this feature is in preview, there are various aspects that are still in flight and that may change before the next preview or when the feature officially ships. For example, we will likely be changing the name of INumber<TSelf>.Create to INumber<TSelf>.CreateChecked and INumber<TSelf>.CreateSaturating to INumber<TSelf>.CreateClamped based on feedback already received. We may also expose new or additional concepts such as IConvertible<TSelf> or interfaces to support vector types and operations.

If any of the above or any other features are important to you or you feel may impact the usability of the feature in your own code, please do provide feedback (.NET Runtime or Libraries, C# Language, and C# Compiler are generally good choices). In particular:

  • Checked operators are not currently possible and so checked(x + y) will not detect overflow: csharplang#4665
  • There is no easy way to go from a signed type to an unsigned type, or vice versa, and so selecting logical (unsigned) vs arithmetic (signed) shift is not possible: csharplang#4682
  • Shifting requires the right-hand side to be System.Int32 and so additional conversions may be required: csharplang#4666
  • All APIs are currently explicitly implemented, many of these will likely become implicitly available on the types when the feature ships

Trying out the features

In order to try out the features there are a few steps required:

  1. Create a new C# console application targeting .NET 6 on the command line or in your favorite IDE

create a new project, Preview Features in .NET 6 – Generic Math

c# console application, Preview Features in .NET 6 – Generic Math

configure your new project, Preview Features in .NET 6 – Generic Math

additional information

  1. Edit the project file to opt into using preview features by setting the EnablePreviewFeatures property to true, and to reference the System.Runtime.Experimental NuGet package.

edit project file, Preview Features in .NET 6 – Generic Math

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <EnablePreviewFeatures>true</EnablePreviewFeatures>
    <LangVersion>preview</LangVersion>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.Runtime.Experimental" Version="6.0.0-preview.7.21377.19" />
  </ItemGroup>

</Project>
  1. Create a generic type or method and constrain it to one of the new static abstract interfaces
// See https://aka.ms/new-console-template for more information

using System.Globalization;

static T Add<T>(T left, T right)
    where T : INumber<T>
{
    return left + right;
}

static T ParseInvariant<T>(string s)
    where T : IParseable<T>
{
    return T.Parse(s, CultureInfo.InvariantCulture);
}

Console.Write("First number: ");
var left = ParseInvariant<float>(Console.ReadLine());

Console.Write("Second number: ");
var right = ParseInvariant<float>(Console.ReadLine());

Console.WriteLine($"Result: {Add(left, right)}");
  1. Run the program and observe the output

First number: 5

Second number: 3.14

Result: 8.14

Closing

While we only briefly covered the new types and gave a simple example of their usage, the potential applications are much broader. We are looking forward to your feedback and seeing what awesome ways you can use this to improve your existing code or in the creation of new code. You can log feedback on any of the existing issues linked above or open new issues, as appropriate, on the relevant GitHub repository (.NET Runtime or Libraries, C# Language, and C# Compiler are generally good choices).

54 comments

Leave a comment

  • Sandro Magi

    Great, it’s something I’ve wanted since the beginning! It’s not clear whether user-defined operators work with these in generic code. Your code samples show operators but maybe that’s just new syntactic sugar over these interfaces. Hopefully one day all operators can be abstracted out in this fashion.

    Is there any performance penalty to calling something like ParseInvariant, other than the standard overhead of dispatching into a generic method? That is, does the CLR generate efficient inline code for each primitive type?

    One of the last remaining pain points that often requires code generation is abstracting over constructors. The CLR missed a great early opportunity to simplify the design over the JVM: just make constructors static methods instead of a distinct kind of method, and permit static interface methods, and there you have constructor abstraction.

    • Steve

      whether user-defined operators work with these in generic code

      Yes

      but maybe that’s just new syntactic sugar over these interfaces

      Nope, they introduced virtual static method dispatch on interfaces from CoreCLR. It’s not syntactic sugar.

      does the CLR generate efficient inline code for each primitive type

      Yes, but sometimes requires profile guided optimization to kick in

      and there you have constructor abstraction

      That’s not possible, a base class can do have constructors that derived classes don’t have. It cannot be abstracted using static interface methods.

      • Tanner Gooding Microsoft employee

        That’s not possible, a base class can do have constructors that derived classes don’t have. It cannot be abstracted using static interface methods.

        While you can’t directly abstract constructors, you will be able to create “factory like” patterns. For example, something like the following is now possible:

        public interface ICreatable<TSelf>
            where TSelf : ICreatable<TSelf>
        {
            static abstract TSelf Create();
        }
        
        public class MyClass : ICreatable<MyClass>
        {
            public static MyClass Create()
            {
                return new MyClass();
            }
        }

        You could imagine this naturally being extended to take additional parameters, similar to Func<T> (e.g. ICreatable<TSelf, TArg>, ICreatable<TSelf, TArg1, TArg2>, etc).

        And, of course, you get all the regular “benefits” of interfaces in that you can explicitly implement them and similar, so they aren’t part of your “normal” API surface

      • Sandro Magi

        That’s not possible, a base class can do have constructors that derived classes don’t have. It cannot be abstracted using static interface methods.

        My point was that abstraction is a core requirement of programming. If you create a concept or make choices that resist or inhibit abstraction, you should reconsider those choices.

        The baggage of constructors that you point out is valid, but it hinges on the inheritance rules of static methods when playing the role as constructors. If constructors become a certain kind of static method, that method kind can follow the constructor inheritance rules rather than the standard rules for static methods.

        This permits abstraction using the same mechanism discussed in this post, and eliminates some of the arbitrary distinctions between methods and constructors.

  • Paulo Pinto

    Welcomed feature, however “static abstract” in interfaces is the best everyone could agree on?

    Slowly I don’t know any longer if I am on C# or C++, I hope that until final release there is a keyword to represent “static abstract” concept.

    • Tanner Gooding Microsoft employee

      I’d recommend filing feedback for the language team at https://github.com/dotnet/csharplang.

      Notably however, static and abstract (and potentially virtual in the future) are all existing concepts with decently well understood meaning. They are also true keywords, not contextual, and so are unambiguous for the compiler to use. This is effectively just combining the two well known concepts and allowing them to be specified together.

    • Miguel de Icaza

      The implementation details are surfacing a bit on the name.

      The beautiful part of this implementation is that it essentially reused everything that was already in place, and the actual implementation for this capability was mostly about removing checks that existed in the VM (“This scenario does not make sense, so do not allow it”). It turns out that it did make sense to support this particular scenario, for precisely this work.

      This feature essentially composed very well with what already existed. The first prototype removed 2 or 3 checks, and everything worked like magic. The real work was in the compiler, where new capabilities were allowed.

      We originally gave it that name purely because that is how the sausage got made, basically, we ended up calling this feature “boiled sausages” instead of hot-dogs, because that is what we were doing: boiling sausages. It might be possible that another name would be more useful – I think the implementation details and the specific bit flipped will be erased from history, it just composes well with the existing idioms and is easy to reason about “static and abstract means you have to implement a static method in a class implementing it”.

  • Charles Roddie

    This feature is really interesting and I look forward to investigating F# support. This could add a lot of consistency and efficiency as a replacement to current generic arithmetic workarounds and then be a good implementation of typeclasses.

    I do hope that you will delete `INumber` before this feature gets released. It is a very silly interface, with things that restrict it to integers (`IIncrementOperators`, `IDecrementOperators`), stuff to do with formatting and parsing bunged in (`ISpanFormattable`…), IUnaryPlusOperators which do nothing, and Create methods which assume that any number is convertible to any other number, which is just not possible. If this type gets into .Net it will lower the intelligence of a lot of programmers.

    • Tanner Gooding Microsoft employee

      I do hope that you will delete INumber before this feature gets released.

      Could you elaborate a bit more on the potential issues you see?

      with things that restrict it to integers (IIncrementOperators, IDecrementOperators)

      Increment and Decrement are also valid operations for floating-point values and most numbers where x + 1 is valid.

      stuff to do with formatting and parsing bunged in (ISpanFormattable…)

      Parsing and formatting are generally good to implement on primitives and simple data like types. It supports display and basic serialization/deserialization to things like the console, debugger, or file streams.

      IUnaryPlusOperators which do nothing

      Yes, but many codebases do use these for code alignment or additional clarity for literals.

      Create methods which assume that any number is convertible to any other number, which is just not possible

      While you certainly can’t convert any type to any other type, there needs to be an API that allows conversion “generically”, otherwise you get stuck with specifying many different constraints which will hurt perf and usability in various scenarios. Exposing a IConvertibleTo<TSelf, TFrom> and/or IConvertibleFrom<TSelf, TFrom> interface for every valid conversion will result in a massive explosion of metadata, particularly for the primitive types where each type is convertible to/from at least 13 other types.

      • Charles Roddie

        Let’s stand back. I would ask the question what does INumber represent? And then make sure that INumber expresses that, expresses nothing else, and that existing types and user-defined types that are numbers by the intended definition can satisfy INumber. Do you agree?

        If INumber is just a way to collect existing functionality of some .Net numeric types, including quirks and hacks, without being usable outside, then it will be a significant failure. Do you agree? You would then have to call it ISystemNumericsNumber.

        • Parsing and formatting: obviously useful sometimes but with no connection to a Number. Someone who wants to implement a type supporting INumber should have no need to implement parsing and formatting operations. Just remove it from INumber and use an IFormattable requirement when needed.

        • IConvertibleTo is not possible to implement as an implementing class cannot know all possible classes which that have and will be created that support INumber and will not be able to convert all of them even if they were statically known. Why do you need any interface? This is just totally unsound. op_Implicit while dangerous is at least sound. If this is retained then you need some way to restrict the supporting types to the 13 or so types you have in mind, preventing other types from implementing INumber, and then the utility is massively reduced.

        • Unary plus: doesn’t exist in mathematics. So it allows certain sorts of formatting in C#. That is a very weak reason to make it an INumber. Better to either remove the ability to format that way (making C# formatting conform to stadard mathematical notation), or have a separate interface ICanBeFormattedWithUnaryPlusInCSharp which types don’t need to implement to satisfy INumber.

        • Adding 1: not useful for floating points and not an unambiguous meaning of “increment”, which could also mean finding the next largest number, but this is a minor point compared to the above.

        • Gilad Freidkin

          I agree with the point about splitting INumber.
          I think it might be a good idea separating INumber into IFormattable and INumber, and so for deriving interfaces of INumber (IBinaryNumber) for example.
          INumber combines two functionalities which can be separated — a thing that will ease the implementation of that interface.
          Most use cases for INumber can be separated into either needing pure INumber (operators) and IFormattable, needing both in the same scenario is rare (as far as I think).

        • Martin Enzelsberger

          I agree that INumber is not an ideal name; the intent is clearly “any of the number types we already have” and probably so users of the feature just have to use that interface and they can use all the functionality of numeric types. It would be much less attractive if users had to specify 3-5 interfaces on their generic constraints.

          But from your point of view, as someone who wants to implement INumber, I agree that there is too much stuff in there that has nothing to do with a mere “number”. Something like your ISystemNumber, IBuiltInNumber, IPredefinedNumberType or so would better express the intent – and INumber should just contain numeric/mathematic functions.

          Where I have to disagree:
          Increment and Decrement operators have always worked on non-integer types like double. It would be more confusing to not have them in an interface when they are available on the actual types. But where I do agree with you, is that they shouldn’t be in the basic INumber interface, but why not in the ISystemNumber one that represents all the built-in types.

          Unary plus: it’s the counterpart of unary minus, that’s why it wouldn’t bother me, even though it has no effect on integers and doubles. Devs that implement INumber won’t build their own integral numeric type, we already have those, they probably build something number-ish, and in such specific use cases an unary + might make sense. Point is, if you do not include it in INumber, where do you? Only in ISystemNumber ? Probably not what a lot of people expect.

          • Tanner Gooding Microsoft employee

            the intent is clearly “any of the number types we already have” and probably so users of the feature just have to use that interface and they can use all the functionality of numeric types

            That was definitely not my intent here. The intent is to provide a contract for what is considered a general “scalar number”. That is a number that can be represented as a single value. I clarify this here as types like vectors or complex numbers can’t clearly be included as they represent multiple values and require additional semantics or considerations from the programmatic point of view.

            If you are defining some scalar number like type, then this can be implemented by the user. That would include concepts like BigInteger, Int128, or other similar number types. To that extent, IScalarNumber might be a better name, but it might also be more confusing to the common consumption case.

            There is also a lot more to numbers than just “math”. From a type system and programmatic point of view, the ability to create a number (from user input or from some other number) and display that number to the user is very important. Most of the usability for the type goes away if you can no longer perform these simple operations and you can’t even reliably write something like LINQs Sum method without them.

            But where I do agree with you, is that they shouldn’t be in the basic INumber interface, but why not in the ISystemNumber one that represents all the built-in types.

            Could you provide an example of where you might have a custom type that would implement INumber but where exposing the concept of increment/decrement doesn’t make sense. In general (and at least for numbers), its largely just a simplification of x += 1.

        • Tanner Gooding Microsoft employee

          INumber is based on the core functionally exposed and expected by consumers of scalar number types. There is a balance between providing too little vs too much, and I believe the current definition is at least decently close. It represents all the central functionality exposed by the built-in number types (such as System.Int32 and System.Double) and supports fairly core scenarios for console, web, and gui applications alike.

          Parsing and formatting: obviously useful sometimes but with no connection to a Number. Someone who wants to implement a type supporting INumber should have no need to implement parsing and formatting operations. Just remove it from INumber and use an IFormattable requirement when needed.

          I’m not sure I’d agree here. Numbers have a textual representation and one of the core concepts for any number type is displaying it to the user and likewise being able to create a number based on user input.

          In real world apps, most inputs come from outside the program. Sometimes that’s from the user, sometimes its from a file (such as CSV), and sometimes its from some external source like a database. Very often and particularly for non-primitive types, this input comes in the form of a string and must be parsed.

          IConvertibleTo is not possible to implement as an implementing class cannot know all possible classes which that have and will be created that support INumber and will not be able to convert all of them even if they were statically known. Why do you need any interface? This is just totally unsound. op_Implicit while dangerous is at least sound. If this is retained then you need some way to restrict the supporting types to the 13 or so types you have in mind, preventing other types from implementing INumber, and then the utility is massively reduced.

          For op_Implicit (or op_Explicit) and ConvertTo the only real difference is the syntax required to use them. Fundamentally, they are both methods taking an input and returning an output.

          While it is indeed not possible to convert a given type to any other type, the concept of creating a value has to be represented somehow and in a way that can be efficiently supported. There must be some mechanism through which you can at least attempt to convert T to U and to fail if that conversion is not supported as otherwise, you can’t even create some T using an integer literal.

          Unary plus: doesn’t exist in mathematics.

          I’m not sure I’d agree here. The plus sign certainly exists in math and not just in programming. But in either case .NET and most programming languages/frameworks it means the identity of x. It is a trivial thing to support and is “best practice” to do so for completeness.

          I do agree it isn’t great that it means users may have to add operator T +(T value) => value to their types if they don’t have that exposed already. To that extent, while .NET 6 does not include support for default implementations of static abstract methods, we are looking at supporting that for the official release. If we do, it is very likely that we will have unary plus implemented by default to simply have the expected behavior and that should simplify this scenario greatly.

          Adding 1: not useful for floating points and not an unambiguous meaning of “increment”, which could also mean finding the next largest number, but this is a minor point compared to the above.

          It is still a supported concept and one with fairly well-defined behavior in .NET. With regards to number types, it functionally means adding 1 and provides a convenience over having to write x += 1.

          • Charles Roddie

            The current INumber definition is way off the mark and languages like F# should have nothing to do with it. We would need reimplement all functionality of everything using INumber because this is an extreme of ugliness, unlike the static interface methods feature itself which is very elegant. I know of repos in C# that define number types and I’m pretty sure they will not like it either.

            “Numbers have a textual representation”. Numbers types don’t need to define a textual representation, and may be used internally in a code base. If they do have textual representations there may be many textual representations not just one. For example a rational number may have a LaTeX or MathML textual representation, and a rational type may support neither, one, or both. Such representations will often be defined outside the type itself since the operations of parsing and formatting are separate to the type. Likewise, numbers will have zero or more binary representations, or any other representations in all sorts of sets, so why are strings given special status?

            You really need to be open to taking feedback from mathematicians here or else the concept of “Number” in .Net is going to be a mess. And from programmers that will want to implement this interface.

            Good examples of interfaces are the many collection interfaces in System.Collections.Generic and System.Collections.Immutable – they do a pretty good job of keeping to the essentials and don’t bung in conversions or parsing and formatting.

            you can’t even reliably write something like LINQs Sum method without them.

            A sum method requires Zero and an addition operation. If LINQ requires anything more than that then raise a bug report with LINQ!

            For op_Implicit (or op_Explicit) and ConvertTo the only real difference is the syntax required to use them. Fundamentally, they are both methods taking an input and returning an output.
            attempt to convert T to U and to fail if that conversion is not supported

            You just stated the difference and it’s essential. One method can fail because it’s not sound and doesn’t conform to any sort of type safety. It should come as no surprise that some people are using .Net, rather than dynamic languages, because they like the safety that comes from static type systems. If INumber breaks that contract since it may fail because of incompatible types.

            plus sign certainly exists in math
            implemented by default

            The plus sign exists in maths but not as a unary operator, since there is no purpose of defining an additional identiy operator. If it’s auto-provided then it will be less of a problem. Similarly if increment is auto-provided (using One and Plus) then that will remove an annoyance.

            complex numbers can’t clearly be included

            A natural comparison operator is the only thing that complex numbers don’t have here. They would usually be considered “scalar numbers”. Taking only the mathematical part of INumber, you have pretty much defined a field, with the nuance that integers are included since they define a division operator that executes a modulus calculation instead of division, and it’s a nice aspect of static interface methods that you can define IField.

            If you define a clean INumber type, that does not prevent you from implementing other interfaces on the primitive .Net number types that you manage, such as IFormattable, and also doesn’t prevent you from defining combinations of interfaces, such as ISystemNumericsNumber which requires INumber and IFormatable and IUnsafeConvertibleFrom etc.. Why wouldn’t that keep everyone happy?

      • Aigner D.

        First: This whole new feature looks amazing, I wanted something like this for a very long time and it’s nice to see that I can implement the things with C# how I always had in my mind but felt “restricted” by the language and had to use workarounds before. I did not vision this particular implementation with static interface methods, but it seems like reasonable solution. Good job!

        But I kinda have to agree with these comments about the use case in the generic math examples, especially combining (I)Numbers with formatting, parsing and also Create/Convert itself from other types seems a bit awkward and too much work for just a number class.

        Sadly I don’t have any good solutions, but I can only ask you to please reconsider these design choices with your team again and maybe you will find a different solution if you talk about it again.

  • Gilad Freidkin

    Can’t figure out how to call an explicit implementation of static interface member (as implemented for primitives).
    I want to call for example: double.Create(), but can’t figure out how.

    • Tanner Gooding Microsoft employee

      They are explicitly implemented in .NET 6 and we will look at making certain APIs implicitly implemented in a future release of .NET.

      In general, you access these members via generics:

      static void Method<T>()
          where T : IBinaryFloatingPoint<T>
      {
          var x = T.Create(...);
          // ... rest of code
      }
      
  • JesperTreetop

    This is great and something I’ve been looking forward to for a long time (coming from Objective-C which has had this with class-level as well as instance-level methods).

    With this shipped, how far away is the similar static abstract members in class hierarchies? Being able to have a static factory method in a base class that is derived in the implementations would be great. Is the issue that, unless the class is also made generic in a similar way, it can’t refer to the Self type of the effective return value?

    I agree that static abstract sounds like a mouthful and also like a logical impossibility according to the original meaning of static, but it does explain how the members are resolved and where they live which comes in handy. static is pretty much the only keyword in C# that refers to members that live on the type level instead of on the instance level.

    • Jared ParsonsMicrosoft employee

      With this shipped, how far away is the similar static abstract members in class hierarchies?

      This is an area that we are looking into in the future. Implementing this required significant more work in the runtime though hence the initial preview was scoped to interfaces only.

    • Tanner Gooding Microsoft employee

      This was an effort primarily among .NET team members and I’m not aware of your prior project. Can you point to that article and design? It would be great to take a look at your approach.

      Notably this was a culmination of past attempts by Microsoft (going back to 2010 and prior), recent investigation by the Xamarin team (January 2020: https://github.com/xamarin/partydonk), and various design work by the various teams starting in June 2020 when “partydonk” was presented in the C# Language Design Meeting (https://github.com/dotnet/csharplang/blob/3c8559f186d4c5df5d1299b0eaa4e139ae130ab6/meetings/2020/LDM-2020-06-29.md). There was also some influence based on what other languages are doing such as protocols in Swift and traits in Rust.

      • Miguel de Icaza

        To add a little more color on the 2010 research.

        Carol Eidt and some other folks had designed a solution that was almost the same solution that we ended up redesigning in January 2020, we were not aware at the time of the specific implementation details, but we did learn of her previous work, and were very happy that we were in alignment around the implementation specifics – which was a good validating point for the design.

        The 2010 capability was not implemented at the time as there were other features competing for attention, and generic math had not risen to be more important.

        Over time, that calculation changed, numerical applications are more common, and deep learning with its myriad of data types was not very pleasant to do in .NET without throwing away code. Once this rolls out, a lot of cumbersome code that we have can be vastly simplified and made more accessible to our users.

        • Richard LanderMicrosoft employee

          I assume this is the article you are referring to, Patrick? https://dev.to/entomy/generics-systems-83n

          Thanks for writing it. It does a good job of covering the problem space and also provides good context from other programming languages. For folks that want a broader view on this topic to help better understand the C# solution, I recommend looking at Patrick’s article. I took away new insight from reading it.

          For fairness, the article does not present a design, more a direction. If you do want to propose a direction or contribute a design (in full or in part), I recommend starting that as an issue on a dotnet repo. It’s fine for an issue to point to a blog post (like yours) as part of the context for a design. We’re just about to start .NET 7 planning. If you’ve got more ideas, now is a great time to engage.

          • Patrick Kelly

            Nope, different article.

            And as for you, ever since we emailed two years ago I’ve been noticing remarkably similar code to what I’ve written show up in the product you manage, so you and I have nothing to speak about. MSFT did this with other projects numerous times, and it’s absolute BS.

          • Richard LanderMicrosoft employee

            Please share the article and same for the other instances. We take these sorts of claims very seriously. I’m assuming that the article must be public for someone to have copied it. If it is a public, sharing the link should be easy.

            You could reasonably describe our design process as being too insular. That’s our bias. I do not believe — on the other end of the spectrum — that anyone on my team is representing the work of others as their own. Our design leaders would never tolerate that happening if they had knowledge of it. They are very rigorous.

            Plagiarism is a serious claim in academic, commercial, or other copyright-oriented domains. I’ll not leave claims of improper attribution uninvestigated or uncontested. Given that you are raising these claims in a public forum, please back them up or I’ll have to consider them (and I’m sure others will as well) hollow. Otherwise, drop the claim if you cannot or will not substantiate it.

      • Jorge Morales Vidal

        I think Patrick is referring to this article: https://dev.to/entomy/real-traits-in-c-4fpk, from September 2020, which is very clever. I saw it on Twitter too: https://twitter.com/pkell7/status/1425593504777089025?s=20

        I suspect his concern is about the style used to propose the new INumber interface and how it resembles the idea in the traits article and the source code of his GitHub projects. So yes, he has a good point.

    • Domingos Dellamonica Jr.

      If you think you are the first to have had this “idea” (about a year ago even!) then you are fooling yourself. Things get reinvented independently all the time and this stuff has been on the radar of a lot of people for a long time.