C# / .NET Guid
On this page
In .NET, UUIDs are called Guids and are represented by System.Guid — a 128-bit value type. The string form is identical to UUIDs from any other platform; the binary serialization order is the one place they differ. See UUID vs GUID.
Generate a v4 Guid
Guid id = Guid.NewGuid();
// 0e6f1b8c-2c33-4f1f-9c0b-2a3d4e5f6a7b
Guid.NewGuid() produces a v4 (random) Guid using the platform’s CSPRNG. Available since .NET Framework 1.0; works identically on .NET Core, .NET 5/6/7/8/9, and Mono.
Generate a v7 Guid (.NET 9+)
// .NET 9.0 (November 2024) and later
Guid id = Guid.CreateVersion7();
// 01928a47-3b30-7c5e-9d1a-f0b8c4a7e923
Guid id2 = Guid.CreateVersion7(timestamp: DateTimeOffset.UtcNow);
CreateVersion7 is the right default for new database primary keys — see UUID as primary key for why. For .NET 8 and earlier, install UUIDNext:
// dotnet add package UUIDNext
using UUIDNext;
Guid id = Uuid.NewDatabaseFriendly(Database.PostgreSql); // v7
v5 Guid (name-based)
.NET has no built-in API for name-based (v5) Guids. Use a package such as UUIDNext:
// dotnet add package UUIDNext
using UUIDNext;
Guid id = Uuid.NewNameBased(Uuid.Namespace.URL, "https://example.com");
Parse from string
Guid id = Guid.Parse("0e6f1b8c-2c33-4f1f-9c0b-2a3d4e5f6a7b");
// Specific format (faster for hot paths)
Guid id2 = Guid.ParseExact("0e6f1b8c2c334f1f9c0b2a3d4e5f6a7b", "N");
// Try-parse for user input
if (Guid.TryParse(input, out Guid parsed)) { /* use parsed */ }
Guid.Parse accepts all five formats (N, D, B, P, X). Use ParseExact if you know the input is one specific shape — it’s faster and rejects the others.
Format specifiers
Guid.ToString() accepts a format character:
| Format | Example | When to use |
|---|---|---|
D (default) | 0e6f1b8c-2c33-4f1f-9c0b-2a3d4e5f6a7b | Logs, JSON, URLs |
N | 0e6f1b8c2c334f1f9c0b2a3d4e5f6a7b | Compact URLs, no hyphens |
B | {0e6f1b8c-2c33-4f1f-9c0b-2a3d4e5f6a7b} | Windows registry, COM |
P | (0e6f1b8c-2c33-4f1f-9c0b-2a3d4e5f6a7b) | Rarely used |
X | {0x0e6f1b8c,0x2c33,0x4f1f,{0x9c,0x0b,...}} | C/C++ struct literal |
Guid.ToString() returns lowercase hex on every .NET version, including .NET Framework. Call .ToUpperInvariant() if you need uppercase.
Bytes — the byte-order gotcha
Guid g = Guid.Parse("00112233-4455-6677-8899-aabbccddeeff");
byte[] bytes = g.ToByteArray();
// 33 22 11 00 55 44 77 66 88 99 aa bb cc dd ee ff
The first three groups are stored little-endian in the byte array. This is “Microsoft order” — it’s the historical .NET / COM convention.
If you’re sending Guids over the wire to a system that expects RFC 4122 byte order (almost everything else), use ToByteArray(bigEndian: true) (.NET 8+) or do the byte-swap yourself:
// .NET 8+
byte[] rfcBytes = g.ToByteArray(bigEndian: true);
// Older
byte[] b = g.ToByteArray();
Array.Reverse(b, 0, 4); // time_low
Array.Reverse(b, 4, 2); // time_mid
Array.Reverse(b, 6, 2); // time_hi_and_version
// b is now RFC byte order
For string round-trips (the canonical form) you don’t need to think about this — it’s purely a binary-serialization concern.
SQL Server
-- table
CREATE TABLE [User] (
Id uniqueidentifier PRIMARY KEY DEFAULT NEWID(),
Email nvarchar(256)
);
-- v4 (random) — bad for clustered index, fine for non-clustered
SELECT NEWID();
-- "sequential" — better for clustered index, but reveals partial timestamp
SELECT NEWSEQUENTIALID();
NEWSEQUENTIALID is not a UUID v7 — it’s a Microsoft-specific sequential variant that increases on one machine, but it can start again from a lower range after Windows restarts, and its values are guessable. For cross-platform v7, generate in C# with Guid.CreateVersion7() and pass the value in (see the SQL Server ordering note under “Common pitfalls”).
Entity Framework Core
public class User
{
public Guid Id { get; set; } = Guid.CreateVersion7(); // .NET 9+
public string Email { get; set; }
}
// Or in OnModelCreating:
modelBuilder.Entity<User>().Property(u => u.Id)
.HasDefaultValueSql("NEWSEQUENTIALID()"); // SQL Server-side
EF Core maps Guid ↔ uniqueidentifier automatically.
ASP.NET Core minimal APIs
app.MapGet("/users/{id:guid}", (Guid id) => GetUser(id));
The :guid route constraint validates the parameter is a parseable Guid before your handler runs. 404 on invalid input — no manual validation needed.
Empty Guid (nil)
Guid.Empty // 00000000-0000-0000-0000-000000000000
Guid.Empty == Guid.Parse("00000000-0000-0000-0000-000000000000") // true
Useful as a sentinel. Don’t store it as a real entity ID; it tends to confuse libraries that treat it specially.
Common pitfalls
Guid.NewGuid()for clustered primary keys is bad on SQL Server — random Guids fragment the B-tree.NEWSEQUENTIALID()is built for SQL Server’s ordering. AGuid.CreateVersion7()value is time-ordered in .NET, but SQL Server comparesuniqueidentifiervalues starting from the last six bytes, so v7 values won’t stay in insert order there. Test before you rely on it, or use a generator made for SQL Server.Guid.ToByteArray()is not RFC byte order. Always specifybigEndian: true(.NET 8+) for cross-platform interop.- Don’t store Guids as
nvarchar(36).uniqueidentifiertakes 16 bytes;nvarchar(36)takes 74 bytes (2 bytes per character plus 2). Guid.Parse("")throwsFormatException. UseTryParsefor user input.
Cheat sheet
| Goal | Code |
|---|---|
| Random Guid | Guid.NewGuid() |
| Time-ordered (DB key, .NET 9+) | Guid.CreateVersion7() |
| Deterministic from name (needs a package) | Uuid.NewNameBased(Uuid.Namespace.URL, name) (UUIDNext) |
| Parse | Guid.Parse(s) or Guid.TryParse(s, out g) |
| Compact (no hyphens) | g.ToString("N") |
| RFC byte order | g.ToByteArray(bigEndian: true) |
| Empty | Guid.Empty |
Try the tools
- UUID generator — works for both UUIDs and Guids (canonical hex)
- UUID v7 generator — time-ordered UUIDs (see the SQL Server note above for clustered keys)
- UUID vs GUID — the byte-order story in detail
- UUID validator — paste a Guid, get version + timestamp