138

I need a unique identifier in .NET (cannot use GUID as it is too long for this case).

Do people think that the algorithm used here is a good candidate or do you have any other suggestions?

6
  • 8
    How short? And how unique? GUID is guaranteed to be unique if based on the hardware address of an ethernet adapter; anything produced purely mathematically can never be provably unique -- just probably unique (with an astronomically high probability).
    – Jon
    Commented Feb 14, 2012 at 14:50
  • 15 in length, and as unique (probably) as possible
    – Noel
    Commented Feb 14, 2012 at 14:55
  • 6
    15 what in length? 15 bytes? If so, why not just strip a byte off a guid..?
    – KristoferA
    Commented Feb 14, 2012 at 14:57
  • If you don't need more than 63 bits worth of keys you can get the same results with a long github.com/joshclark/Flakey Commented Aug 25, 2016 at 17:59
  • 4
    @KristoferA stripping off a byte in a guid astronomically raises your chances of key collisions. If you strip off the wrong ordered byte it might make it certain to collide. Commented Aug 25, 2016 at 18:00

22 Answers 22

150

This one a good one - http://www.singular.co.nz/blog/archive/2007/12/20/shortguid-a-shorter-and-url-friendly-guid-in-c-sharp.aspx

and also here YouTube-like GUID

You could use Base64:

string base64Guid = Convert.ToBase64String(Guid.NewGuid().ToByteArray());

That generates a string like E1HKfn68Pkms5zsZsvKONw==. Since a GUID is always 128 bits, you can omit the == that you know will always be present at the end and that will give you a 22 character string. This isn't as short as YouTube though.

5
  • 19
    A short note: if this is needed for URL's, whoever uses this might want to sanitize the '+' and the '/' characters as well
    – tkit
    Commented Aug 8, 2016 at 11:08
  • 4
    madskristensen's blog Commented Aug 6, 2018 at 23:05
  • 2
    I wanted a max 23 char long unique id for UnnyNet in Unity, and I was stuck with my big dumb GUIDs, and you made me so happy :) Commented Aug 27, 2019 at 19:34
  • Interestingly (if you're working with the Zoom API), this is almost certainly how they generate their UUIDs. They're 22 characters long and are base64 encoded.
    – vgel
    Commented Sep 16, 2020 at 17:11
  • simple and very helpful, thanks!
    – TDiblik
    Commented Feb 8, 2022 at 14:33
57

I use a similar approach as Dor Cohen's but removing some special characters:

var uid = Regex.Replace(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), "[/+=]", "");     

This will output just alphanumeric characters. The UIDs are not guaranteed to have always the same length. Here is a sample run:

vmKo0zws8k28fR4V4Hgmw 
TKbhS0G2V0KqtpHOU8e6Ug 
rfDi1RdO0aQHTosh9dVvw
3jhCD75fUWjQek8XRmMg 
CQUg1lXIXkWG8KDFy7z6Ow 
bvyxW5aj10OmKA5KMhppw
pIMK8eq5kyvLK67xtsIDg
VX4oljGWpkSQGR2OvGoOQ 
NOHBjUUHv06yIc7EvotRg
iMniAuUG9kiGLwBtBQByfg
2
  • 19
    You will loose some properties guaranteed by GUIDs by throwing away information like this. I would recommend replacing the characters you are not comfortable with by different characters while preserving bijection between GUIDs and their serialization format. Commented Dec 7, 2017 at 13:14
  • 4
    This is a nice one to use if you DONT want to convert back to a GUID but just need random characters for something else.. for example tracking workers in multi threads, or Logging Prefix for threaded objects, etc.
    – Piotr Kula
    Commented Dec 19, 2017 at 13:24
52
var ticks = new DateTime(2016,1,1).Ticks;
var ans = DateTime.Now.Ticks - ticks;
var uniqueId = ans.ToString("x");

Keep a baseline date (which in this case is 1st Jan 2016) from when you will start generating these ids. This will make your ids smaller.

Generated Number: 3af3c14996e54

8
  • milliseconds is always 0 for that DateTime object
    – Teejay
    Commented May 23, 2017 at 8:43
  • Also remove the last sentence.
    – Teejay
    Commented May 24, 2017 at 16:01
  • 2
    oneliner: var uniqueId = (DateTime.Now.Ticks - new DateTime(2016, 1, 1).Ticks).ToString("x");
    – Sgedda
    Commented May 17, 2019 at 9:04
  • 15
    No good for generation of ids almost at the same time like in a for-loop.e.g. dotnetfiddle.net/L3MIgZ
    – Jaider
    Commented Nov 19, 2019 at 1:40
  • just tried locally DateTime.Now.Ticks never returns the same value. Why does the dotnetfiddle behaves differently? Commented Oct 21, 2021 at 5:23
37

Simple usable package. I use it for temporal request id generator.

https://www.nuget.org/packages/shortid

https://github.com/bolorundurowb/shortid

Uses System.Random

string id = ShortId.Generate();
// id = KXTR_VzGVUoOY

(from the github page)

If you want to control the type of id generated by specifying whether you want numbers, special characters and the length, call the Generate method and pass three parameters, the first a boolean stating whether you want numbers, the second a boolean stating whether you want special characters, the last a number indicating your length preference.

string id = ShortId.Generate(true, false, 12);
// id = VvoCDPazES_w
13

Just in case merely removing hyphens will do for anyone:

Guid.NewGuid().ToString("n")

This generates perfectly unique strings of 32 characters:

5db4cee3bfd8436395d37fca2d48d5b3
82fac271c76148a3a0667c00a5da990d
12

As far as I know, just stripping off a part of a GUID isn't guaranteed to be unique - in fact, it's far from being unique.

The shortest thing that I know that guarantees global uniqueness is featured in this blog post by Jeff Atwood. In the linked post, he discusses multiple ways to shorten a GUID, and in the end gets it down to 20 bytes via Ascii85 encoding.

However, if you absolutely need a solution that's no longer than 15 bytes, I'm afraid you have no other choice than to use something which is not guaranteed to be globally unique.

8

For my local app I'm using this time based approach:

/// <summary>
/// Returns all ticks, milliseconds or seconds since 1970.
/// 
/// 1 tick = 100 nanoseconds
/// 
/// Samples:
/// 
/// Return unit     value decimal           length      value hex       length
/// --------------------------------------------------------------------------
/// ticks           14094017407993061       17          3212786FA068F0  14
/// milliseconds    1409397614940           13          148271D0BC5     11
/// seconds         1409397492              10          5401D2AE        8
///
/// </summary>
public static string TickIdGet(bool getSecondsNotTicks, bool getMillisecondsNotTicks, bool getHexValue)
{
    string id = string.Empty;

    DateTime historicalDate = new DateTime(1970, 1, 1, 0, 0, 0);

    if (getSecondsNotTicks || getMillisecondsNotTicks)
    {
        TimeSpan spanTillNow = DateTime.UtcNow.Subtract(historicalDate);

        if (getSecondsNotTicks)
            id = String.Format("{0:0}", spanTillNow.TotalSeconds);
        else
            id = String.Format("{0:0}", spanTillNow.TotalMilliseconds);
    }
    else
    {
        long ticksTillNow = DateTime.UtcNow.Ticks - historicalDate.Ticks;
        id = ticksTillNow.ToString();
    }

    if (getHexValue)
        id = long.Parse(id).ToString("X");

    return id;
}
7

22 chars, url safe, and retains Guid uniqueness.

// Our url safe, base 64 alphabet:
const string alphabet = "-_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

// Sanitized Guid string. Preserve the last two hex chars
var guidStr = "929F7C4D4B2644E1A122A379C02D6345";
var lastTwo = guidStr.Substring(30, 2);

string shortGuid = "";

// Iterate over the ten groups of 3 hex chars: 929 F7C 4D4 B26 44E 1A1 22A 379 C02 D63
for (var i = 0; i < 10; i++)
{
    var hex = guidStr.Substring(i*3, 3);              // Get the next 3 hex chars
    var x = Convert.ToInt32(hex, 16);                 // Convert to int
    shortGuid += $"{alphabet[x/64]}{alphabet[x%64]}"; // Lookup the two-digit base64 value
}
shortGuid += lastTwo; // Don't forget the last two

Console.WriteLine(shortGuid);

Output:

yDXWhiGAfc4v6EbTK0Px45
2
  • Not the downvoter, but not sure how this is any different to the accepted answer, it's just a different way without the explicit conversion to Base64.
    – phuzi
    Commented Nov 18, 2020 at 16:24
  • 4
    My intent was to provide a solution that shows the algorithm. There are ways with fewer lines of code, clearly. But it's always good to know how something works. This solution would be easily portable to any other language.
    – Didaxis
    Commented Nov 18, 2020 at 16:29
6

IDENTITY values should be unique in a database, but you should be aware of the limitations... for example, it makes bulk data inserts basically impossible which will slow you down if you're working with a very large number of records.

You may also be able to use a date/time value. I've seen several databases where they use the date/time to be the PK, and while it's not super clean - it works. If you control the inserts, you can effectively guarantee that the values will be unique in code.

5

Based on some others, here is my solution which provides a different encoded guid which is URL (and Docker) safe and does not loose any information:

Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace("=", "").Replace("+", "-").Replace("/", "_");

Example outputs are:

BcfttHA780qMdHSxSBoZFA
_4p5srPgOE2f25T_UnoGLw
H9xR_zdfm0y-zYjdR3NOig
1
  • It replaces some characters with alternatives that work in a scenario that the value is neede for URLs or Docker names. As it replaces, you do not loose any information.
    – Roemer
    Commented Nov 19, 2020 at 10:37
4

If your app dont have a few MILLIION people, using that generate short unique string at the SAME MILLISECOND, you can think about using below function.

private static readonly Object obj = new Object();
private static readonly Random random = new Random();
private string CreateShortUniqueString()
{
    string strDate = DateTime.Now.ToString("yyyyMMddhhmmssfff");
    string randomString ;
    lock (obj)
    {
        randomString = RandomString(3);
    }
    return strDate + randomString; // 16 charater
}
private string RandomString(int length)
{

    const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxy";
    var random = new Random();
    return new string(Enumerable.Repeat(chars, length)
      .Select(s => s[random.Next(s.Length)]).ToArray());
}

change yyyy to yy if you just need to use your app in next 99 year.
Update 20160511: Correct Random function
- Add Lock object
- Move random variable out of RandomString function
Ref

1
  • 3
    This is great although you shouldn't initialise a new Random every time - the reason for the lock is to allow you to re-use the same Random instance. I think you forgot to delete that line!
    – NibblyPig
    Commented May 6, 2017 at 19:19
4
    public static string ToTinyUuid(this Guid guid)
    {
        return Convert.ToBase64String(guid.ToByteArray())[0..^2]  // remove trailing == padding 
            .Replace('+', '-')                          // escape (for filepath)
            .Replace('/', '_');                         // escape (for filepath)
    }

Usage

Guid.NewGuid().ToTinyUuid()

It's not rocket science to convert back, so I'll leave you that much.

0
3

here my solution, is not safe for concurrency, no more of 1000 GUID's per seconds and thread safe.

public static class Extensors
{

    private static object _lockGuidObject;

    public static string GetGuid()
    {

        if (_lockGuidObject == null)
            _lockGuidObject = new object();


        lock (_lockGuidObject)
        {

            Thread.Sleep(1);
            var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            var epochLong = Convert.ToInt64((DateTime.UtcNow - epoch).TotalMilliseconds);

            return epochLong.DecimalToArbitrarySystem(36);

        }

    }

    /// <summary>
    /// Converts the given decimal number to the numeral system with the
    /// specified radix (in the range [2, 36]).
    /// </summary>
    /// <param name="decimalNumber">The number to convert.</param>
    /// <param name="radix">The radix of the destination numeral system (in the range [2, 36]).</param>
    /// <returns></returns>
    public static string DecimalToArbitrarySystem(this long decimalNumber, int radix)
    {
        const int BitsInLong = 64;
        const string Digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        if (radix < 2 || radix > Digits.Length)
            throw new ArgumentException("The radix must be >= 2 and <= " + Digits.Length.ToString());

        if (decimalNumber == 0)
            return "0";

        int index = BitsInLong - 1;
        long currentNumber = Math.Abs(decimalNumber);
        char[] charArray = new char[BitsInLong];

        while (currentNumber != 0)
        {
            int remainder = (int)(currentNumber % radix);
            charArray[index--] = Digits[remainder];
            currentNumber = currentNumber / radix;
        }

        string result = new String(charArray, index + 1, BitsInLong - index - 1);
        if (decimalNumber < 0)
        {
            result = "-" + result;
        }

        return result;
    }

code not optimized, just sample!.

1
  • While an interesting solution, there's no guarantee that UtcNow returns a unique tick value for every millisecond: per the remarks, the resolution depends on the system timer. Additionally, you'd better make sure the system clock doesn't change backward! (Since user13971889's answer bumped this question to the top of my feed, and I critiqued that answer, I figure I should repeat that criticism here.)
    – Joe Sewell
    Commented Jul 21, 2020 at 20:00
3

Here's my small method to generate a random and short unique id. Uses a cryptographic rng for secure random number generation. Add whatever characters you need to the chars string.

using System;
using System.Security.Cryptography;

// ...

private string GenerateRandomId(int length)
{
    string charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    char[] outputChars = new char[length];
    
    using RandomNumberGenerator rng = RandomNumberGenerator.Create();
    int minIndex = 0;
    int maxIndexExclusive = charset.Length;
    int diff = maxIndexExclusive - minIndex;

    long upperBound = uint.MaxValue / diff * diff;

    byte[] randomBuffer = new byte[sizeof(int)];

    for (int i = 0; i < outputChars.Length; i++)
    {
        // Generate a fair, random number between minIndex and maxIndex
        uint randomUInt;
        do
        {
            rng.GetBytes(randomBuffer);
            randomUInt = BitConverter.ToUInt32(randomBuffer, 0);
        }
        while (randomUInt >= upperBound);
        int charIndex = (int)(randomUInt % diff);

        // Set output character based on random index
        outputChars[i] = charset[charIndex];
    }

    return new string(outputChars);
}

This works by scaling a random integer down to the range of charset indices, and accounts for the edge case where the random number is the absolute upper bound by rerolling for a new integer.

This solution produces fair and evenly distributed output, tested with a 1,000,000 character long output showing no obvious biases:

string output = GenerateRandomId(1_000_000);
var tally = output.GroupBy(c => c).OrderBy(g => g.Key).Select(g => (g.Key, g.Count())).ToArray();

int average = (int)(tally.Aggregate(new BigInteger(0), (b, t) => {b += t.Item2; return b;}, b => b) / tally.Count());
int max = tally.Max(g => g.Item2);
int min = tally.Min(g => g.Item2);

Console.WriteLine($"Avg: {average}");
Console.WriteLine($"Max: {max}");
Console.WriteLine($"Min: {min}");


foreach((char key, int count) in tally) {
    Console.WriteLine($"{key}: {count}");
}

Output:

Avg: 27777
Max: 28163
Min: 27341
0: 28081
1: 27773
...
Z: 27725
2
  • I found this is very useful for small samples, but I was curious how it creates unique values. I tested and foud they are not unique. For example, with length=5 there are about 100 duplicates per 100k. But with length=7, duplicates are rare so it must be a matter of probabilities. I could not find a function that generates unique strings without duplicate checking, but I'm curious if that exists.
    – pghcpa
    Commented Oct 24, 2021 at 5:33
  • @pghcpa This is possible, what you need is a 1:1 transform function that takes an integer input and produces a unique integer output. I.e, every input in a range of integers produces a single unique output within that range, but make the output appear to be uniformly random with a linearly increasing input. Then, convert the large output integer into base-36, which becomes the random string. The downside is that it can become easy to derive the transform function used, and then trivial to predict future rng valus, so it's not very secure.
    – Ryan
    Commented Oct 25, 2021 at 1:41
2

I know it's quite far from posted date... :)

I have a generator which produces only 9 Hexa characters, eg: C9D6F7FF3, C9D6FB52C

public class SlimHexIdGenerator : IIdGenerator
{
    private readonly DateTime _baseDate = new DateTime(2016, 1, 1);
    private readonly IDictionary<long, IList<long>> _cache = new Dictionary<long, IList<long>>();

    public string NewId()
    {
        var now = DateTime.Now.ToString("HHmmssfff");
        var daysDiff = (DateTime.Today - _baseDate).Days;
        var current = long.Parse(string.Format("{0}{1}", daysDiff, now));
        return IdGeneratorHelper.NewId(_cache, current);
    }
}


static class IdGeneratorHelper
{
    public static string NewId(IDictionary<long, IList<long>> cache, long current)
    {
        if (cache.Any() && cache.Keys.Max() < current)
        {
            cache.Clear();
        }

        if (!cache.Any())
        {
            cache.Add(current, new List<long>());
        }

        string secondPart;
        if (cache[current].Any())
        {
            var maxValue = cache[current].Max();
            cache[current].Add(maxValue + 1);
            secondPart = maxValue.ToString(CultureInfo.InvariantCulture);
        }
        else
        {
            cache[current].Add(0);
            secondPart = string.Empty;
        }

        var nextValueFormatted = string.Format("{0}{1}", current, secondPart);
        return UInt64.Parse(nextValueFormatted).ToString("X");
    }
}
0
1

Based on @dorcohen's answer and @pootzko's comment. You can use this. It is safe over the wire.

var errorId = System.Web.HttpServerUtility.UrlTokenEncode(Guid.NewGuid().ToByteArray());
2
1

In C# a long value has 64 bits, which if encoded with Base64, there will be 12 characters, including 1 padding =. If we trim the padding =, there will be 11 characters.

One crazy idea here is we could use a combination of Unix Epoch and a counter for one epoch value to form a long value. The Unix Epoch in C# DateTimeOffset.ToUnixEpochMilliseconds is in long format, but the first 2 bytes of the 8 bytes are always 0, because otherwise the date time value will be greater than the maximum date time value. So that gives us 2 bytes to place an ushort counter in.

So, in total, as long as the number of ID generation does not exceed 65536 per millisecond, we can have an unique ID:

// This is the counter for current epoch. Counter should reset in next millisecond
ushort currentCounter = 123;

var epoch = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
// Because epoch is 64bit long, so we should have 8 bytes
var epochBytes = BitConverter.GetBytes(epoch);
if (BitConverter.IsLittleEndian)
{
    // Use big endian
    epochBytes = epochBytes.Reverse().ToArray();
}

// The first two bytes are always 0, because if not, the DateTime.UtcNow is greater 
// than DateTime.Max, which is not possible
var counterBytes = BitConverter.GetBytes(currentCounter);
if (BitConverter.IsLittleEndian)
{
    // Use big endian
    counterBytes = counterBytes.Reverse().ToArray();
}

// Copy counter bytes to the first 2 bytes of the epoch bytes
Array.Copy(counterBytes, 0, epochBytes, 0, 2);

// Encode the byte array and trim padding '='
// e.g. AAsBcTCCVlg
var shortUid = Convert.ToBase64String(epochBytes).TrimEnd('=');
1
  • Please don't use time for uniqueness. I've already been bitten twice by people using this in multiprocessor systems.
    – Jay
    Commented Oct 6, 2022 at 15:04
1

to not lose characters (+ / -) and if you want to use your guid in an url, it must be transformed into base32

for 10 000 000 no duplicate key

    public static List<string> guids = new List<string>();
    static void Main(string[] args)
    {
        for (int i = 0; i < 10000000; i++)
        {
            var guid = Guid.NewGuid();
            string encoded = BytesToBase32(guid.ToByteArray());
            guids.Add(encoded);
            Console.Write(".");
        }
        var result = guids.GroupBy(x => x)
                    .Where(group => group.Count() > 1)
                    .Select(group => group.Key);

        foreach (var res in result)
            Console.WriteLine($"Duplicate {res}");

        Console.WriteLine($"*********** end **************");
        Console.ReadLine();
    }

    public static string BytesToBase32(byte[] bytes)
    {
        const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        string output = "";
        for (int bitIndex = 0; bitIndex < bytes.Length * 8; bitIndex += 5)
        {
            int dualbyte = bytes[bitIndex / 8] << 8;
            if (bitIndex / 8 + 1 < bytes.Length)
                dualbyte |= bytes[bitIndex / 8 + 1];
            dualbyte = 0x1f & (dualbyte >> (16 - bitIndex % 8 - 5));
            output += alphabet[dualbyte];
        }

        return output;
    }
0

If you dont need to type the string you could use the following:

static class GuidConverter
{
    public static string GuidToString(Guid g)
    {
        var bytes = g.ToByteArray();
        var sb = new StringBuilder();
        for (var j = 0; j < bytes.Length; j++)
        {
            var c = BitConverter.ToChar(bytes, j);
            sb.Append(c);
            j++;
        }
        return sb.ToString();
    }

    public static Guid StringToGuid(string s) 
        => new Guid(s.SelectMany(BitConverter.GetBytes).ToArray());
}

This will convert the Guid to a 8 character String like this:

{b77a49a5-182b-42fa-83a9-824ebd6ab58d} --> "䦥띺ᠫ䋺ꦃ亂檽趵"

{c5f8f7f5-8a7c-4511-b667-8ad36b446617} --> "엸詼䔑架펊䑫ᝦ"

-2
private static readonly object _getUniqueIdLock = new object();
public static string GetUniqueId()
{       
    lock(_getUniqueIdLock)
    {
        System.Threading.Thread.Sleep(1);
        return DateTime.UtcNow.Ticks.ToString("X");
    }
}
2
  • 1
    While an interesting solution, there's no guarantee that UtcNow returns a unique tick value for every millisecond: per the remarks, the resolution depends on the system timer. Additionally, you'd better make sure the system clock doesn't change backward! (ur3an0's answer also have these issues.)
    – Joe Sewell
    Commented Jul 21, 2020 at 19:53
  • Agreed. This is a poor man's approach and shouldn't be used beyond your own well controlled environment. Commented Jul 22, 2020 at 0:09
-3

you can use

code = await UserManager.GenerateChangePhoneNumberTokenAsync(input.UserId, input.MobileNumber);

its 6 nice characters only, 599527 ,143354

and when user virify it simply

var result = await UserManager.VerifyChangePhoneNumberTokenAsync(input.UserId, input.Token, input.MobileNumber);

hope this help you

2
  • I always keep my passwords simple, easy to remember
    – Toolkit
    Commented Oct 15, 2019 at 12:38
  • 3
    This deserves more down votes Commented Apr 20, 2022 at 21:45
-8
Guid.NewGuid().ToString().Split('-').First()
1

Not the answer you're looking for? Browse other questions tagged or ask your own question.