C#を用いたアプリケーション開発において、数値の基数変換は頻繁に発生する処理の一つです。

特にデバイス制御、ネットワークプロトコル解析、あるいは画像処理といった低レイヤーに近い領域では、(10進数)(16進数)の相互変換が欠かせません。

C#は進化を続けており、単純な変換メソッドだけでなく、現代的な高パフォーマンスな書き方も提供されています。

本記事では、初心者が押さえるべき基本コードから、大量のデータを扱う際に重要なメモリ効率を意識した最適解まで、実務で役立つテクニックを網羅的に解説します。

10進数から16進数への変換:基本の書き方

C#において、整数(10進数)を16進数の文字列に変換する最も一般的な方法は、ToStringメソッドを使用することです。

このメソッドは、引数に書式指定子を渡すことで、出力を柔軟に制御できます。

ToStringメソッドによる書式指定

数値型のToStringメソッドに"X"または"x"を指定すると、その数値が16進数文字列として出力されます。

大文字の “X” を指定すれば結果は大文字に、小文字の “x” を指定すれば小文字になります。

C#
int value = 255;

// 大文字の16進数に変換
string hexUpper = value.ToString("X"); 

// 小文字の16進数に変換
string hexLower = value.ToString("x");

// 桁数を固定(2桁)し、足りない場合は0で埋める
string hexZeroPadding = value.ToString("X2");

Console.WriteLine($"Default (X): {hexUpper}");
Console.WriteLine($"Default (x): {hexLower}");
Console.WriteLine($"Zero Padding (X2): {hexZeroPadding}");
実行結果
Default (X): FF
Default (x): ff
Zero Padding (X2): FF

Convertクラスを利用した変換

System.Convertクラスを使用する方法もあります。

こちらは、基数を明示的に指定できるため、(2進数や8進数への変換)と書き方を統一したい場合に便利です。

C#
int value = 4096;

// 第2引数に基数(16)を指定
string hexString = Convert.ToString(value, 16).ToUpper();

Console.WriteLine($"Convert Result: {hexString}");
実行結果
Convert Result: 1000

ただし、Convert.ToStringは戻り値が常に小文字になるため、大文字が必要な場合は別途ToUpper()を呼び出す必要がある点に注意してください。

16進数から10進数への変換:基本の書き方

文字列として表現された16進数を数値(10進数)に戻すには、Parse系メソッド、またはConvertクラスを使用します。

int.Parse と NumberStyles の組み合わせ

最も一般的で推奨される方法は、int.Parseまたはint.TryParseを使用する方法です。

第2引数に System.Globalization.NumberStyles.HexNumber を指定することが必須です。

C#
string hexValue = "1A2B";

// 16進数文字列をint型に変換
int result = int.Parse(hexValue, System.Globalization.NumberStyles.HexNumber);

Console.WriteLine($"Hex {hexValue} to Decimal: {result}");
実行結果
Hex 1A2B to Decimal: 6699

Convert.ToInt32 による変換

Convert.ToInt32を使用する場合、第2引数に基数として16を渡します。

この方法のメリットは、文字列が"0x"というプレフィックス(接頭辞)を持っていても適切に処理できる点にあります。

C#
string hexWithPrefix = "0x7F";

// Convertクラスなら0xが含まれていても変換可能
int result = Convert.ToInt32(hexWithPrefix, 16);

Console.WriteLine($"Hex {hexWithPrefix} to Decimal: {result}");
実行結果
Hex 0x7F to Decimal: 127

int.Parseで “0x” 付きの文字列を処理しようとすると例外が発生するため、入力データの形式に応じて使い分けることが重要です。

パフォーマンス重視の最適解:SpanとUtf8Formatterの活用

現代のC#開発、特に.NET 8以降の環境では、「いかにしてヒープメモリの割り当て(アロケーション)を減らすか」がパフォーマンス向上の鍵となります。

従来のToStringは呼び出すたびに新しい文字列オブジェクトを生成しますが、大量のデータをループ内で処理する場合、これはGC(ガベージコレクション)の負荷に繋がります。

TryFormat によるゼロアロケーション変換

数値型には、ISpanFormattableインターフェースが実装されており、TryFormatメソッドを利用することで、(スタックメモリ上のSpanに対して直接書き込む)ことが可能です。

C#
int value = 12345678;
Span<char> buffer = stackalloc char[8]; // スタック上にバッファを確保

// 文字列を生成せずにSpanへ16進数を書き込む
if (value.TryFormat(buffer, out int charsWritten, "X"))
{
    ReadOnlySpan<char> result = buffer.Slice(0, charsWritten);
    Console.WriteLine($"Encoded Hex: {result.ToString()}");
}

この手法を使えば、一時的な文字列生成を完全に排除できるため、高頻度で実行されるロジック内では非常に有効な最適解となります。

Utf8Formatter による高速変換

ネットワーク通信などでUTF-8バイト列として16進数を扱いたい場合は、System.Buffers.Text.Utf8Formatterを使用するのが最も効率的です。

C#
using System.Buffers.Text;

int value = 30000;
Span<byte> utf8Buffer = stackalloc byte[10];

// 数値を直接UTF-8形式の16進数バイト列に変換
if (Utf8Formatter.TryFormat(value, utf8Buffer, out int bytesWritten, new StandardFormat('X')))
{
    Console.WriteLine($"Bytes Written: {bytesWritten}");
    // 実際にはここから直接ネットワークストリーム等に書き込む
}

特殊なケース:巨大な数値とバイト配列の変換

通常のintlongの範囲を超える数値を扱う場合や、バイナリデータを一括で変換する場合についても触れておきます。

BigInteger による巨大な16進数変換

System.Numerics.BigInteger型を使用すれば、(事実上無制限の大きさの数値)と16進数の相互変換が可能です。

C#
using System.Numerics;

// 非常に長い16進数文字列
string largeHex = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
BigInteger largeValue = BigInteger.Parse(largeHex, System.Globalization.NumberStyles.HexNumber);

Console.WriteLine($"Large Value: {largeValue}");

バイト配列(byte[])と16進数文字列の変換

暗号化処理やハッシュ計算の結果を16進数文字列で表示したい場合は、Convert.ToHexStringが非常に便利です。

このメソッドは.NET 5以降で導入された高速な変換専用メソッドです。

メソッド名用途
Convert.ToHexString(bytes)バイト配列を16進数文字列に一括変換
Convert.FromHexString(hexString)16進数文字列をバイト配列に一括逆変換
C#
byte[] data = { 0, 15, 255, 128 };
string hex = Convert.ToHexString(data);

Console.WriteLine($"Hex Data: {hex}");
実行結果
Hex Data: 000FFF80

16進数変換における注意点

変換処理を実装する際、いくつか陥りやすい落とし穴があります。

  1. 負の数の扱い
    int型の負の数を16進数に変換すると、2の補数表現となります。例えば、(-1).ToString("X")"FFFFFFFF" を返します。これを再び数値に戻す際は、期待する型に合わせて解析する必要があります。
  2. エンディアンの影響
    BitConverter.GetBytesなどを用いてバイト配列を経由して変換する場合、実行環境のエンディアン(リトルエンディアンかビッグエンディアンか)によってバイト順が逆転することがあります。
  3. オーバーフロー
    16進数文字列がint.MaxValueを超える大きさの場合、int.ParseOverflowExceptionをスローします。入力される可能性がある値の範囲を考慮し、longBigIntegerの利用を検討してください。

まとめ

C#における10進数と16進数の変換は、単純な表示目的からパフォーマンスが要求されるシステム実装まで、多様なアプローチが存在します。

  • 基本の変換:ToString("X")int.Parse で十分対応可能。
  • 柔軟な処理:Convertクラスを使えば接頭辞付き文字列や基数指定にも対応。
  • 高負荷環境:Span<char>TryFormat を活用してメモリ割り当てを最小限に抑える。
  • バイナリ一括処理:Convert.ToHexString などの専用APIを優先する。

「目的の精度」と「処理するデータ量」に応じて適切なメソッドを選択することが、洗練されたC#プログラムを書くための第一歩です。

まずは最もシンプルな方法から始め、パフォーマンスがボトルネックとなる箇所にピンポイントで高度な手法を適用していくのが良いでしょう。