12

I have an application that has a guid variable which needs to be unique (of course). I know that statistically any guid should just be assumed to be unique, but due to dev/test environment reasons, the same value may be seen multiple times. So when that happens, I want to "increment" the value of the Guid, rather than just creating a whole new one. There does not seem to be a simple way to do this. I found a hack, which I will post as a possible answer, but want a cleaner solution.

Abacus
  • 2,041
  • 1
  • 18
  • 23
  • Why can't it be a new Guid? – a-ctor May 22 '15 at 20:01
  • 7
    That's *not* how `GUID`s are *supposed to be* handled. What is wrong with calling `Guid.NewGuid()` when you need a new one? – Der Kommissar May 22 '15 at 20:01
  • The way you are using this, you might want to consider calling it a UUID instead. – VoidStar May 22 '15 at 20:05
  • Aren't you looking for a sequential GUID: http://stackoverflow.com/questions/1752004/sequential-guid-generator-c-sharp ? – atlaste May 22 '15 at 20:05
  • 1
    @EBrown the main problem with non-sequential GUID's is that they don't work well with b-trees used in databases. More exact: they drain the performance from the system. Also, IIRC sequential GUID's are perfectly valid GUID's if you use the correct implementation... but to be honest I'm not 100% sure (can't remember the details from the spec). – atlaste May 22 '15 at 20:06
  • Yes, looked it up. Sequential GUID's are normally implemented as type 1 UUID's from the spec: http://tools.ietf.org/rfc/rfc4122.txt . – atlaste May 22 '15 at 20:09
  • @EBrown As I explained in the question, I want to increment when there is a collision. This log is for humans to see, so the human could see that the guids were nearly identical, and the database is satisfied that they are unique. – Abacus Jul 31 '15 at 19:07
  • @atlaste No, the original guid is generated randomly, and the point is address when a collision occurs, by creating a nearly identical value that is also unique. – Abacus Jul 31 '15 at 19:08
  • 2
    @Abacus so basically you want a Global unique sequential IDs. Well it is possible, since Google does that using timestamps in Spanner -- but they have dedicated hardware for that (including stuff like geo locators and atom clocks and so on, it's quite a fancy paper to read :-). For normal people like me and probably you, it's just not going to happen with the "Global Unique" constraint. If you drop that constraint, might as well use a sequencer, since they are designed to do this. – atlaste Jul 31 '15 at 20:12

4 Answers4

14

You can get the byte components of the guid, so you can just work on that:

static class GuidExtensions
{
    private static readonly int[] _guidByteOrder =
        new[] { 15, 14, 13, 12, 11, 10, 9, 8, 6, 7, 4, 5, 0, 1, 2, 3 };
    public static Guid Increment(this Guid guid)
    {
        var bytes = guid.ToByteArray();
        bool carry = true;
        for (int i = 0; i < _guidByteOrder.Length && carry; i++)
        {
            int index = _guidByteOrder[i];
            byte oldValue = bytes[index]++;
            carry = oldValue > bytes[index];
        }
        return new Guid(bytes);
    }
}

EDIT: now with correct byte order

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • 1
    Why the downvotes? care to explain what's wrong with this answer? – Thomas Levesque May 22 '15 at 20:12
  • This code throws an OverflowException with the ++ when the byte's value is 255. So would need to have unchecked{} around that statement. – Abacus Jul 31 '15 at 16:33
  • @Abacus, is your project compiled with the `/checked` option? Integer arithmetic is unchecked with the default options. – Thomas Levesque Jul 31 '15 at 23:37
  • Yes, it is checked in my project. Or rather, it is not unchecked. Using VS2010 Premium, and it's under project properties, Compile, Advanced, and I have "Remove integer overflow checks" left unchecked. This seems the sensible default -- it seems very strange to me that the default behavior would be unchecked. – Abacus Sep 01 '15 at 16:08
9

Thanks to Thomas Levesque's byte order, here's a nifty LINQ implementation:

static int[] byteOrder = { 15, 14, 13, 12, 11, 10, 9, 8, 6, 7, 4, 5, 0, 1, 2, 3 };

static Guid NextGuid(Guid guid)
{
    var bytes = guid.ToByteArray();
    var canIncrement = byteOrder.Any(i => ++bytes[i] != 0);
    return new Guid(canIncrement ? bytes : new byte[16]);
}

Note it wraps around to Guid.Empty if you manage to increment it that far.

It would be more efficient if you were to keep incrementing a single copy of bytes rather than calling ToByteArray on each GUID in turn.

Community
  • 1
  • 1
Rawling
  • 49,248
  • 7
  • 89
  • 127
1

Possible solution -- I think this works (not really tested), but want a better solution.

public static Guid Increment(this Guid value)
{
    var bytes = value.ToByteArray();
    // Note that the order of bytes in the returned byte array is different from the string representation of a Guid value.
    //  Guid:       00112233-4455-6677-8899-aabbccddeeff
    //  byte array: 33 22 11 00 55 44 77 66 88 99 AA BB CC DD EE FF
    // So the byte order of the following indexes indicates the true low-to-high sequence
    if (++bytes[15] == 0) if (++bytes[14] == 0) if (++bytes[13] == 0) if (++bytes[12] == 0) if (++bytes[11] == 0) if (++bytes[10] == 0) // normal order
     if (++bytes[9] == 0) if (++bytes[8] == 0) // normal order
      if (++bytes[6] == 0) if (++bytes[7] == 0) // reverse order
       if (++bytes[5] == 0) if (++bytes[4] == 0) // reverse order
        if (++bytes[3] == 0) if (++bytes[2] == 0) if (++bytes[1] == 0) { ++bytes[0]; } // reverse order
    return new Guid(bytes);
}

Edit: here is the code I ended up using; props to the answers above for the general technique, although without the "unchecked" clause they both would throw exceptions in some cases. But I also tried to make the below as readable as possible.

private static int[] _guidByteOrder = { 15, 14, 13, 12, 11, 10, 9, 8, 6, 7, 4, 5, 0, 1, 2, 3 };
public static Guid NextGuid(this Guid guid)
{
    var bytes = guid.ToByteArray();
    for (int i = 0; i < 16; i++)
    {
        var iByte = _guidByteOrder[i];
        unchecked { bytes[iByte] += 1; }
        if (bytes[iByte] != 0)
            return new Guid(bytes);
    }
    return Guid.Empty;
}
Abacus
  • 2,041
  • 1
  • 18
  • 23
1

Verified Solution for Ordered Strings:

    private static Guid Increment(Guid guid)
    {

        byte[] bytes = guid.ToByteArray();

        byte[] order = { 15, 14, 13, 12, 11, 10, 9, 8, 6, 7, 4, 5, 0, 1, 2, 3 };

        for (int i = 0; i < 16; i++)
        {
            if (bytes[order[i]] == byte.MaxValue)
            {
                bytes[order[i]] = 0;
            }
            else
            {
                bytes[order[i]]++;
                return new Guid(bytes);
            }
        }

        throw new OverflowException("Congratulations you are one in a billion billion billion billion etc...");

    }

Verification:

    private static Guid IncrementProof(Guid guid, int start, int end)
    {

        byte[] bytes = guid.ToByteArray();

        byte[] order = { 15, 14, 13, 12, 11, 10, 9, 8, 6, 7, 4, 5, 0, 1, 2, 3 };

        for (int i = start; i < end; i++)
        {
            if (bytes[order[i]] == byte.MaxValue)
            {
                bytes[order[i]] = 0;
            }
            else
            {
                bytes[order[i]]++;
                return new Guid(bytes);
            }
        }

        throw new OverflowException("Congratulations you are one in a billion billion billion billion etc...");

    }

    static void Main(string[] args)
    {

        Guid temp = new Guid();

        for (int j = 0; j < 16; j++)
        {
            for (int i = 0; i < 255; i++)
            {
                Console.WriteLine(temp.ToString());
                temp = IncrementProof(temp, j, j + 1);
            }
        }

    }
Dominic
  • 11
  • 2