C#を用いたアプリケーション開発において、データの識別子として欠かせない存在がGuid(Globally Unique Identifier)です。

Guidは、ネットワーク全体や異なるシステム間においても重複することのない一意な値を生成することを目的として設計されています。

データベースの主キーやファイル名、セッションIDなど、その用途は多岐にわたります。

近年では、従来のランダムなGuid(UUID v4)に加えて、時系列でのソートが可能なUUID v7が.NET 9から正式にサポートされるなど、より高度なパフォーマンスを求めるシステムでの活用シーンが広がっています。

本記事では、C#におけるGuidの基礎から、最新のv7対応、パフォーマンスを意識した最適化手法までを網羅的に解説します。

Guid(UUID)の基礎知識

Guidとは「Globally Unique Identifier」の略称であり、128ビット(16バイト)の数値で構成される識別子です。

RFC 4122(および後継のRFC 9562)で定義されているUUID(Universally Unique Identifier)と事実上同じものを指します。

C#において、Guidは System.Guid 構造体として定義されており、値型であるためメモリ効率も良く、等価比較などの操作も高速に行えます。

Guidの最大の特徴は、中央集権的な管理サーバーを必要とせず、オフライン環境であっても「世界で一つだけのID」を高い確率で生成できる点にあります。

128ビットという巨大な値の範囲(約3.4×10の38乗)により、衝突が発生する確率は実用上無視できるほど低く抑えられています。

C#におけるGuidの内部構造

System.Guid 構造体は内部的に、4バイトの整数、2本の2バイト整数、および8バイトのバイト配列によって構成されています。

これらが組み合わさることで、32文字の16進数として表現される標準的な形式(例:550e8400-e29b-41d4-a716-446655440000)を形成します。

Guidの基本的な生成方法

C#でGuidを扱う際、最も頻繁に使用されるのが「新しいGuidの生成」と「空のGuidの取得」です。

1. Guid.NewGuid() による生成

通常、新しい一意のIDが必要な場合は Guid.NewGuid() メソッドを使用します。

このメソッドは、暗号学的に強力な乱数ジェネレーターを使用して、RFC 4122のUUID バージョン4(完全ランダム)に基づいたGuidを生成します。

C#
using System;

class Program
{
    static void Main()
    {
        // 新しいGuidを生成
        Guid newId = Guid.NewGuid();
        
        Console.WriteLine($"Generated Guid: {newId}");
    }
}
実行結果
Generated Guid: 3f2504e0-4f89-11d3-9a0c-0305e82c3301

2. Guid.Empty による「空」の表現

初期値や「値が存在しないこと」を示すために、すべてのビットが0であるGuidが必要な場合があります。

この場合は Guid.Empty を使用します。

これは new Guid("00000000-0000-0000-0000-000000000000") と同等です。

C#
// 空のGuidを取得
Guid emptyId = Guid.Empty;

if (emptyId == Guid.Empty)
{
    Console.WriteLine("The Guid is empty.");
}

最新のUUID v7対応(.NET 9以降)

これまでの Guid.NewGuid()(UUID v4)は、完全にランダムであるため、データベースのインデックス(特にB-Treeインデックス)に使用すると、挿入時に物理的な並べ替えが発生し、パフォーマンスの低下を招くという課題がありました。

この問題を解決するために登場したのが、UUID v7です。

.NET 9以降では、このv7形式のGuidを標準で生成できるようになりました。

UUID v7の特徴

UUID v7は、タイムスタンプ(ミリ秒精度)をその構造の前半部分に含んでいます。

これにより、以下のメリットが得られます。

  1. 時系列でのソートが可能: 生成された順番に並べ替えることができます。
  2. DBインデックスに最適: 常に「末尾への挿入」に近い挙動となるため、ページ分割を防ぎ、書き込みパフォーマンスが劇的に向上します。
  3. 一意性の維持: タイムスタンプの後に乱数セクションが含まれるため、同一時刻に生成しても衝突しません。

v7の生成コード

.NET 9(および .NET 9 SDKをターゲットとするプロジェクト)では、Guid.CreateVersion7() メソッドが使用可能です。

C#
using System;

class Program
{
    static void Main()
    {
        // UUID v7を生成 (.NET 9以降)
        Guid v7Id = Guid.CreateVersion7();
        
        // 特定のタイムスタンプを指定して生成することも可能
        DateTimeOffset timestamp = DateTimeOffset.UtcNow;
        Guid customV7Id = Guid.CreateVersion7(timestamp);

        Console.WriteLine($"UUID v7: {v7Id}");
    }
}
実行結果
UUID v7: 01923456-789a-7abc-8def-0123456789ab

※先頭部分はタイムスタンプに基づいているため、連続して生成すると値が増加していく様子が確認できます。

Guidの書式指定と文字列変換

Guidを文字列として出力する際、用途に応じてハイフンの有無や括弧の有無を切り替えることができます。

ToString() メソッドに特定の書式指定子を渡すことで、柔軟にフォーマットを変更可能です。

主な書式指定子一覧

指定子形式の説明出力例
D32桁の16進数をハイフンで区切る(既定)00000000-0000-0000-0000-000000000000
Nハイフンなしの32桁00000000000000000000000000000000
Bハイフンあり、波括弧 {} で囲む{00000000-0000-0000-0000-000000000000}
Pハイフンあり、丸括弧 () で囲む(00000000-0000-0000-0000-000000000000)
X16進数表記の配列形式{0x00000000,0x0000,0x0000,{0x00,0x00,...}}

実装例

C#
Guid id = Guid.NewGuid();

Console.WriteLine("D: " + id.ToString("D"));
Console.WriteLine("N: " + id.ToString("N"));
Console.WriteLine("B: " + id.ToString("B"));

文字列からのパース(解析)

外部から受け取った文字列をGuid型に変換する場合、Parse または TryParse を使用します。

Parse メソッド

形式が正しいことが保証されている場合は Parse を使用しますが、不正な形式の場合は例外(FormatException)がスローされます。

C#
string input = "d39ec2a2-b941-4770-988c-f09623910620";
Guid parsedId = Guid.Parse(input);

TryParse メソッド

ユーザー入力などの不確実な文字列を扱う場合は、例外を避けるために TryParse を使用するのがベストプラクティスです。

C#
string input = "invalid-guid-string";

if (Guid.TryParse(input, out Guid result))
{
    Console.WriteLine($"Successfully parsed: {result}");
}
else
{
    Console.WriteLine("Invalid Guid format.");
}

バイナリデータとの相互変換

ネットワーク通信やファイル保存など、Guidをバイト配列(byte[])として扱いたい場合があります。

Guidからバイト配列へ

ToByteArray() メソッドを使用します。

これにより16バイトの配列が取得できます。

C#
Guid id = Guid.NewGuid();
byte[] bytes = id.ToByteArray();

Console.WriteLine($"Byte array length: {bytes.Length}"); // 常に16

バイト配列からGuidへ

コンストラクタにバイト配列を渡すことで、Guidを復元できます。

C#
byte[] bytes = new byte[16]; // 16バイトのデータ
Guid id = new Guid(bytes);

注意: エンディアン(バイト順)の違いに注意してください。

Windows環境の ToByteArray() は、内部構造の一部をリトルエンディアンで出力します。

他の言語(JavaやPythonなど)とバイナリレベルで通信する場合、このバイト順の違いが原因で不一致が発生することがあります。

パフォーマンスの最適化:Span<char>の活用

高頻度でGuidを文字列化したり、パースしたりするWeb APIなどのシステムでは、文字列の割り当て(アロケーション)を減らすことが重要です。

最新のC#では Span<T> を活用した最適化が可能です。

TryFormat による高速な文字列化

ToString() は内部で新しい文字列インスタンスを生成しますが、TryFormat を使用すれば、既存のバッファ(スタックに割り当てた Span<char> など)に直接書き込むことができ、GC(ガベージコレクション)の負荷を最小限に抑えられます。

C#
Guid id = Guid.NewGuid();
Span<char> buffer = stackalloc char[36]; // ハイフンあり形式の長さ

if (id.TryFormat(buffer, out int charsWritten, "D"))
{
    // buffer に直接書き込まれる
    Console.WriteLine(buffer.ToString());
}

データベース(Entity Framework Core)でのGuidの扱い

データベースでGuidを扱う際、SQL Serverなどの一部のRDBMSでは uniqueidentifier 型が用意されています。

Entity Framework Core (EF Core) を使用すると、これらを簡単にマッピングできます。

モデル定義の例

C#
public class User
{
    // 自動的に主キーとして認識される
    public Guid Id { get; set; }
    
    public string Name { get; set; }
}

UUID v7との組み合わせ

前述の通り、標準の Guid.NewGuid() はDBのパフォーマンスを低下させる可能性があります。

EF Core 9以降では、モデルの構成でデフォルト値をv7に設定するようなアプローチが推奨されます。

C#
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>()
        .Property(u => u.Id)
        .HasDefaultValueSql("NEWID()"); // SQL Serverの例(※V7対応はDB側実装に依存)
}

※アプリケーション側で生成して保存する場合は、Id = Guid.CreateVersion7() をコンストラクタやサービス層で呼び出すようにします。

まとめ

C#におけるGuidは、単純な一意の識別子としての役割を超え、システムのパフォーマンスやスケーラビリティに直結する重要な要素となっています。

  • 基本生成: 通常は Guid.NewGuid() を使用する。
  • 最新トレンド: データベースの主キーなど、ソート順が重要な箇所では .NET 9で導入された Guid.CreateVersion7() の採用を検討する。
  • 安全性: パースには TryParse を使用し、無効な形式による実行時エラーを防ぐ。
  • 最適化: 高負荷な処理では TryFormatSpan<T> を活用して、メモリ割り当てを削減する。

これらの手法を適切に使い分けることで、堅牢かつ高速なアプリケーションを構築することが可能になります。

特にUUID v7の登場は、これまで「Guidか、それとも連番のLong型か」という開発者の悩みに終止符を打つ、非常に強力なアップデートと言えるでしょう。