.NET7 の Generic Math についてメモ

# はじめに

先日の VisualStudio の更新によって、`C#11` と `.NET 7` が扱える環境が整ったので、`.NET 7` でついに導入された **Generic Math** と呼ばれるものについてメモを書いていきます。

# Generic Math とは？

`.NET` には以前から `IEquatable<T>` や `IComparable<T>` があったものの、ジェネリクスなクラス・構造体に対して算術演算子や比較演算子を用いることができなかったのですが、それができるようになりました。

```算術演算子の利用.cs
public T Max<T>(T a, T b) where T : INumber<T> {
    return (a > b) ? a : b;
}
```

この **Generic Math** は、`C#11` でインターフェースに静的仮想メンバーを定義できるようになったのと同時に実装されました。

`C#11` の新機能によって、インターフェースに以下のような定義をすることが可能となりました。

```静的仮想メンバーの例.cs
public interface ISomething {
    public static abstract int ParseString(string s);
    public static abstract operator +(int l, int r);
    public static abstract explicit operator int(long val);
}
```

`ISomething` を継承したクラス・構造体は、上記で定義されている静的メンバをすべて実装する必要があります。

# Generic Math で追加されたインターフェース

**Generic Math** によって、かなり多くのインターフェースが新たに追加されています。

これによって、公式ドキュメントの `System.Int32` の説明が載っている [ページ](https://docs.microsoft.com/ja-jp/dotnet/api/system.int32?view=net-7.0) を見てもわかる通り、継承されているインターフェースの量が大変なことになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1299544/819460de-cbf5-303e-17cb-c9375ed24970.png)

# 各インターフェースが定義する演算子

## 算術演算子

以下のインターフェースの型パラメーターは、`TSelf (演算子) TOther = TResult` の関係になっています。

- `IAdditionOperators<TSelf, TOther, TResult>` -- `+` 演算子
- `ISubtractionOperators<TSelf, TOther, TResult>` -- `-` 演算子
- `IMultiplyOperators<TSelf, TOther, TResult>` -- `*` 演算子
- `IDivisionOperators<TSelf, TOther, TResult>` -- `/` 演算子
- `IMudulusOperators<TSelf, TOther, TResult>` -- `%` 演算子

以下のインターフェースの型パラメーターは、`TSelf(演算子) = TSelf` または `(演算子)TSelf = TSelf` の関係になっています。

- `IIncrementOperators<TSelf>` -- `++` 演算子
- `IDecrementOperators<TSelf>` -- `--` 演算子

## 単項算術演算子

これは、以下のような文脈で使用されます。 (`-foo` の部分)

```単項算術演算子の例.cs
public void Example() {
    int foo = 15;
    Console.WriteLine(-foo); // -15 が出力される
}
```

以下のインターフェースの型パラメーターは、`(演算子)TSelf = TSelf` の関係になっています。

- `IUnaryPlusOperators<TSelf>` -- `+` 演算子
- `IUnaryNegationOperatos<TSelf>` -- `-` 演算子

## ビット演算子

以下のインターフェースの型パラメーターは、`TSelf (演算子) TOther = TResult` の関係になっています。

- `IBitwiseOperators<TSelf, TOther, TResult>` -- `&`, `|`, `^`, `~` 演算子
- `IShiftOperators<TSelf, TOther, TResult>` -- `<<`, `>>`, `<<<` 演算子

## 比較演算子

以下のインターフェースの型パラメーターは、`TSelf (演算子) TOther = TResult` の関係になっています。

ただし、`TResult` には基本的に `bool` が入ります。

- `IComparisonOperators<TSelf, TOther, TResult>` -- `>`, `>=`, `<`, `<=` 演算子
- `IEqualityOperators<TSelf, TOther, TResult>` -- `==`, `!=` 演算子

# 用途に応じた制約の決め方

`INumber<TSelf>` を制約に指定しておけば、算術演算子や比較演算子が利用できるので、 それを指定すれば基本的には大丈夫だと思われます。

## 整数だけ

`IBinaryInteger<TSelf>` を制約に加えると、整数型のみが指定できるようになります。

`IBinaryInteger<TSelf>` の `TSelf` には `INumber<TSelf>` の制約も掛けられているので、 `INumber<TSelf>` を制約に加える必要はないです。
(コードを見た時の分かりやすさを重視するなら `INumber<TSelf>` も制約に加えてもいいかもしれないです)

## 符号付きの数だけ

`ISignedNumber<TSelf>` を制約に加えると、符号付きの型のみが指定できるようになります。

:::note warn
`ISignedNumber<TSelf>` の `TSelf` には `INumber<TSelf>` **ではなく** `INumberBase<TSelf>` の制約が掛けられているので、 `INumber<TSelf>` で使用できる演算子やメソッドなどを利用したい場合はそれを制約に加える必要があります。
:::

## 符号無しの数だけ

`IUnsignedNumber<TSelf>` を制約に加えると、符号無しの型のみが指定できるようになります。

:::note warn
`ISignedNumber<TSelf>` と同じく、 `TSelf` には `INumberBase<TSelf>` の制約が掛けられています。
:::

# 何が何を継承してるのかさっぱり分かない問題

どのインターフェースが何を継承しえているのか、その関係が全然分からず困っていましたが、ufcppさんがGitHubでわかりやすい図を作られていたのでここで共有させてもらおうと思います。

以下のリンクから該当ページに飛べます。

<https://github.com/ufcpp/UfcppSample/issues/354#issuecomment-1166550011>