Neu in .NET 7 [10]: Generische Mathematik

.NET 6.0 hat einige generische Mathematikoperationen für beliebige Zahlentypen eingeführt, die im aktuellen .NET produktionsreif sind.

In Pocket speichern vorlesen Druckansicht 1 Kommentar lesen

(Bild: Bankrx / Shutterstock.com)

Lesezeit: 1 Min.
Von
  • Dr. Holger Schwichtenberg

Generische Mathematik umfasst in .NET 7.0 einige Schnittstellen im Basisklassennamensraum System.Numerics, die es erlauben, mathematische Operationen so zu implementieren, dass sie für beliebige Zahlentypen funktionieren – Ganzzahlen und gebrochener Zahlen beliebiger Bit-Länge.

Die in .NET 6.0 als experimentell gekennzeichneten generischen Mathematikoperationen wie INumber<T>, INumberBase<T>, IComparisonOperators<T, T>, IAdditionOperators<T, T, T>, IMultiplyOperators<T, T, T> und ISubtractionOperators<T, T, T> haben mit .NET 7.0 die Produktionsreife erlangt.

Das nächste Listing zeigt ein aussagekräftiges Beispiel für eine generische mathematische Berechnung in der Methode Calc() und ein generisches Extrahieren einer Zahl aus einer Zeichenkette in ParseNumber(). Dabei kommt unter anderem der in .NET 7.0 neu eingeführten Zahlentyp System.Int128 (Ganzzahl, 16 Bytes) zum Einsatz.

using System.Globalization;
using System.Numerics;
 
namespace CS11;
 
public class CS11_GenericMath
{
 T Calc<T>(T x, T y)
  where T : INumber<T> // Neues Interface mit 
                       // static abstract Members!
 {
  Console.WriteLine(
    $"Calc {x.GetType().ToString()}/{y.GetType().ToString()}");
  if (x == T.Zero || y <= T.Zero) return T.One;
  return (x + y) * T.CreateChecked(42.24);
 }
 
 T ParseNumber<T>(string s)
    where T : IParsable<T>
 {
  return T.Parse(s, CultureInfo.InvariantCulture);
 }
 
 public void Run()
 {
  CUI.H2("Calc mit 1 und 2");
  Console.WriteLine($"Ergebnis mit System.Byte: 
    {Calc((byte)1, (byte)2)}"); // 126
  Console.WriteLine($"Ergebnis mit System.Int32: 
    {Calc(1, 2)}"); // 126
  Console.WriteLine($"Ergebnis mit System.Int128: 
    {Calc( (Int128)1, (Int128)2)}"); // 126
  Console.WriteLine($"Ergebnis mit System.Single: 
    {Calc((Single)1.0, (Single)2.0)}"); // 126,72
  Console.WriteLine($"Ergebnis mit System.Double: 
    {Calc(1.0d, 2.0d)}"); // 126,72
  Console.WriteLine($"Ergebnis mit System.Decimal: 
    {Calc(1.0m, 2.0m)}"); // 126,720
  Console.WriteLine($"Ergebnis mit System.Half: 
    {Calc((Half)1.0m, (Half)2.0m)}"); // 126,75
 
  CUI.H2("ParseNumber 1.00 und 2.00");
  var x = ParseNumber<float>("1.00");
  var y = ParseNumber<float>("2.00");
 
  Console.WriteLine($"Ergebnis mit System.Single: 
    {Calc(x, y)}"); // 3,6000001
  Console.WriteLine($"Ergebnis mit System.Int32: 
    {Calc(0, 1)}"); // 1
 }
}

Der Beitrag der Programmiersprache C# ist an dieser Stelle die Möglichkeit, statische abstrakte Mitglieder in Schnittstellen zu definieren, was seit C# 10.0 experimentell möglich war und seit C# 11.0 offiziell zur Sprachsyntax gehört. Diesen Modifizierer verwendet Microsoft in den Basisklassen wie INumberBase<T>.

public interface INumberBase<TSelf>
        : IAdditionOperators<TSelf, TSelf, TSelf>,
          IAdditiveIdentity<TSelf, TSelf>,
          IDecrementOperators<TSelf>,
          IDivisionOperators<TSelf, TSelf, TSelf>,
          IEquatable<TSelf>,
          IEqualityOperators<TSelf, TSelf, bool>,
          IIncrementOperators<TSelf>,
          IMultiplicativeIdentity<TSelf, TSelf>,
          IMultiplyOperators<TSelf, TSelf, TSelf>,
          ISpanFormattable,
          ISpanParsable<TSelf>,
          ISubtractionOperators<TSelf, TSelf, TSelf>,
          IUnaryPlusOperators<TSelf, TSelf>,
          IUnaryNegationOperators<TSelf, TSelf>
        where TSelf : INumberBase<TSelf>?
    {
/// <summary>Gets the value <c>1</c> for the type.</summary>
static abstract TSelf One { get; }

/// <summary>Gets the value <c>0</c> for the type.</summary>
static abstract TSelf Zero { get; }
…

/// <summary>Tries to parses a string into a value.</summary>
static abstract bool TryParse([NotNullWhen(true)] string? s,
                               NumberStyles style, 
                               IFormatProvider? provider,
                               out TSelf result);
}

(rme)