C# - NumericUpDownコントロールはHexadecimal=trueで使用するときはMaximumプロパティとMinimumプロパティをそれぞれInt32.MaxValueと.Int32.MinValueにする制約事項があるが、負数の表示がなぜかlong型になっている

# まえおき

追加調査した結果タイトルを見直しました。

旧タイトル：
C# - NumericUpDownコントロールはHexadecimal=trueで使用すると0x80000000が負になる(せっかくValueがDecimal型なのに入力値がInt32でConvertされる)

# 概要

`NumericUpDown`コントロールは下記のように、数値の入力をUp/Down操作するのに使いやすいコントロールです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/e2674783-2c99-ad87-1c59-5ab5be26c21f.png)

# 経緯

マイコンのシミュレータをフルスクラッチでつくろうというクレイジーなことをやり始めたときにはまった話。。

`NumericUpDown`コントロールは`Hexadecimal`を`true`にすると16進法で数値を表示できるのですが、32bit以上を扱おうとすると厄介な問題が発生しました。

`Maximum`を`0xFFFFFFFF`とかにし、`Minimum`を0にして、スピン（？）（コントロールの右端の上下の▲のボタン）をいじってるときはよかったんですが、、

コントロールに直接`FFFFFFFF`とかを入力すると、`0`になるんですよ。。。　えっ？？？
ってなりましたが、さすがにもうこの手の現象には慣れてきて、どうせ内部でInt32に変換しているんだろうなと思ったら案の定でした。。。

# 内部コード

ILSpyで確認してみた。

```csharp

/// <summary>スピン ボックス (アップダウン コントロール) に表示するテキストを数値に変換して評価します。</summary>
protected void ParseEditText()
{
	try
	{
		if (!string.IsNullOrEmpty(Text) && (Text.Length != 1 || !(Text == "-")))
		{
			if (Hexadecimal)
			{
				Value = Constrain(Convert.ToDecimal(Convert.ToInt32(Text, 16)));
			}
			else
			{
				Value = Constrain(decimal.Parse(Text, CultureInfo.CurrentCulture));
			}
		}
	}
	catch
	{
	}
	finally
	{
		base.UserEdit = false;
	}
}
```


ヤッパリネ。。なんでInt32やねん・・・。けちくさい・・・。


# 追調査

コメントを頂いて、`Hexadecimal`を`true`に設定して使用した際の振る舞いとか、
今回やりたいこと（`0`～`0xFFFFFFFF`[^1]（あえて型は問わず、16進数8桁を入力・設定できるユーザインタフェースを`Form`に置きたい。）ができないか追加で調べてみた。

[^1]: もしくは`0x80000000`( $= -2^{31} = $`Int32.MinValue`)～`0x7FFFFFFF`($= 2^{31}-1=$ `Int32.MaxValue`)


## そもそもドキュメントに制約が記載されている

しっかり制約事項が書かれていました。。。※日本語サイトのほうは訳が壊れていたので英語のほうを引用しています。
https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.numericupdown.hexadecimal?view=netcore-3.1#remarks

> When the Hexadecimal property is set, the UpdateEditText method is called to update the spin box's display to the new format.
<b><u>When the Hexadecimal property is set to true, the Maximum property should be set to Int32.MaxValue and the Minimum property should be set to Int32.MinValue.</u></b>

## ドキュメントの通り使ってみたら・・・

```csharp

using System;
using System.Drawing;
using System.Windows.Forms;

class NudHexTest : Form
{
    NumericUpDown nud;

    NudHexTest()
    {
        Controls.Add(nud = new NumericUpDown(){
            Width = 180,
            Hexadecimal = true,
            Maximum =  Int32.MaxValue, //  0x7FFFFFFF
            Minimum =  Int32.MinValue, // -0x80000000
            Value   =  0
        });
        ClientSize = new Size(180, 100);
    }
    
    [STAThread]
    static void Main(string[] args)
    {
        Application.Run(new NudHexTest());
    }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/2220a0b0-4ab9-4c3b-63df-34f26ae20b8c.png)

▼を押したら`FFFFFFFF`が表示される（今回の使い方が一応実現できる）と思ったら、16桁になった。。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/64db0bda-4342-22d1-8d9f-0dbaf0e86639.png)

### 内部コード

```csharp

public decimal Value
{
	get
	{
		if (base.UserEdit)
		{
			ValidateEditText();
		}
		return currentValue;
	}
	set
	{
		if (value != currentValue)
		{
			if (!initializing && (value < minimum || value > maximum))
			{
				throw new ArgumentOutOfRangeException("Value", SR.GetString("InvalidBoundArgument", "Value", value.ToString(CultureInfo.CurrentCulture), "'Minimum'", "'Maximum'"));
			}
			currentValue = value;
			OnValueChanged(EventArgs.Empty);
			currentValueChanged = true;
			UpdateEditText();
		}
	}
}
```

↓

```csharp

/// <summary>スピン ボックス (アップダウン コントロール) の現在の値を適切な形式で表示します。</summary>
protected override void UpdateEditText()
{
	if (!initializing)
	{
		if (base.UserEdit)
		{
			ParseEditText();
		}
		if (currentValueChanged || (!string.IsNullOrEmpty(Text) && (Text.Length != 1 || !(Text == "-"))))
		{
			currentValueChanged = false;
			base.ChangingText = true;
			Text = GetNumberText(currentValue);
		}
	}
}
```

↓

```csharp

private string GetNumberText(decimal num)
{
	if (Hexadecimal)
	{
		return ((long)num).ToString("X", CultureInfo.InvariantCulture);
	}
	return num.ToString((ThousandsSeparator ? "N" : "F") + DecimalPlaces.ToString(CultureInfo.CurrentCulture), CultureInfo.CurrentCulture);
}
```

なぜかこっちは`long` (`Int64`)。。

# 参考

[Decimal構造体 - Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/api/system.decimal?view=netcore-3.1)