Generate CUIDs in C#

Resources  |  Generate CUIDs in C#

To generate a CUID (Collision-resistant Unique Identifier) in C#, we need to follow the algorithm's principles to ensure that the generated identifiers are unique, readable, and collision-resistant. While there isn't a built-in CUID library in C#, you can create a custom implementation.

Here's a step-by-step guide and example code to generate a CUID in C#:

Steps to Generate a CUID in C#

  1. Timestamp: Use the current timestamp to ensure uniqueness over time.
  2. Counter: Use a counter to handle multiple CUIDs generated in a short period.
  3. Fingerprint: Use a machine-specific value to avoid collisions across different machines.
  4. Randomness: Add random characters to further reduce the risk of collisions.
  5. Base36 Encoding: Encode the components using Base36 for readability.

Example Code

using System;
using System.Security.Cryptography;
using System.Text;

public class CUIDGenerator
{
    private static readonly char[] Base36Chars = "0123456789abcdefghijklmnopqrstuvwxyz".ToCharArray();
    private static readonly object LockObject = new object();
    private static long LastTimeStamp = 0;
    private static int Counter = 0;

    public static string GenerateCUID()
    {
        long timestamp = GetCurrentTimeStamp();
        int counter = GetNextCounter(timestamp);

        string timestampPart = EncodeBase36(timestamp);
        string counterPart = EncodeBase36(counter);
        string fingerprintPart = GetMachineFingerprint();
        string randomPart = GetRandomString(4);

        return $"c{timestampPart}{counterPart}{fingerprintPart}{randomPart}";
    }

    private static long GetCurrentTimeStamp()
    {
        return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
    }

    private static int GetNextCounter(long timestamp)
    {
        lock (LockObject)
        {
            if (timestamp == LastTimeStamp)
            {
                return ++Counter;
            }
            else
            {
                LastTimeStamp = timestamp;
                Counter = 0;
                return Counter;
            }
        }
    }

    private static string EncodeBase36(long value)
    {
        StringBuilder result = new StringBuilder();
        while (value > 0)
        {
            result.Insert(0, Base36Chars[value % 36]);
            value /= 36;
        }
        return result.ToString().PadLeft(8, '0');
    }

    private static string GetMachineFingerprint()
    {
        string machineName = Environment.MachineName;
        byte[] hashBytes;
        using (var md5 = MD5.Create())
        {
            hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(machineName));
        }
        StringBuilder sb = new StringBuilder();
        foreach (byte b in hashBytes)
        {
            sb.Append(Base36Chars[b % 36]);
        }
        return sb.ToString().Substring(0, 4);
    }

    private static string GetRandomString(int length)
    {
        var random = new RNGCryptoServiceProvider();
        byte[] data = new byte[length];
        random.GetBytes(data);
        StringBuilder sb = new StringBuilder(length);
        foreach (byte b in data)
        {
            sb.Append(Base36Chars[b % 36]);
        }
        return sb.ToString();
    }
}

class Program
{
    static void Main()
    {
        string cuid = CUIDGenerator.GenerateCUID();
        Console.WriteLine(cuid); // Example output: c0000016f4r1z6j3dh3a
    }
}

Explanation

  1. Timestamp Generation:

    • GetCurrentTimeStamp method returns the current timestamp in milliseconds since the Unix epoch.
  2. Counter Management:

    • GetNextCounter method ensures that the counter is incremented if multiple CUIDs are generated within the same millisecond.
  3. Base36 Encoding:

    • EncodeBase36 method converts a number to a Base36 encoded string, ensuring compact and readable identifiers.
  4. Machine Fingerprint:

    • GetMachineFingerprint method generates a machine-specific fingerprint using the MD5 hash of the machine name.
  5. Random String:

    • GetRandomString method generates a random string using a cryptographically secure random number generator.
  6. Combining Components:

    • GenerateCUID method combines the timestamp, counter, fingerprint, and random parts to form the final CUID.

Summary

This implementation follows the CUID algorithm to generate unique, collision-resistant, and human-readable identifiers in C#. The approach combines the current timestamp, a counter, a machine-specific fingerprint, and random characters, all encoded in Base36, to ensure uniqueness and readability.