在C#中递增Guid

12

我有一个需要保证唯一性的GUID变量。虽然从统计学的角度来看,任何GUID都应该被视为唯一的,但由于开发/测试环境的原因,同样的值可能会出现多次。因此,当出现这种情况时,我想“增加”GUID的值,而不仅仅是创建一个全新的。目前似乎没有简单的方法来实现这个目标。我找到了一种hack方式,可以作为一种可能的答案,但我想要一种更简洁的解决方案。


7
“GUID”不应该以那种方式被处理。当您需要一个新的“GUID”时,调用“Guid.NewGuid()”有什么问题吗? - Der Kommissar
1
@EBrown,非顺序GUID的主要问题在于它们与数据库中使用的B树不兼容。更确切地说,它们会降低系统的性能。此外,如果您使用正确的实现,我记得顺序GUID是完全有效的GUID...但老实说,我不确定100%(无法记住规范的细节)。 - atlaste
@EBrown,正如我在问题中所解释的那样,我想在发生冲突时进行递增。这个日志是供人类查看的,所以人类可以看到GUID几乎相同,而数据库则满足它们是唯一的。 - Abacus
@atlaste 不,原始GUID是随机生成的,当发生冲突时,通过创建一个几乎相同但也是唯一的值来解决地址问题。 - Abacus
2
@Abacus 所以基本上你想要全局唯一的顺序 ID。嗯,这是可能的,因为 Google 在 Spanner 中使用时间戳来实现这一点 - 但他们有专门的硬件用于此(包括地理定位器和原子钟等等,这是一篇相当不错的论文 :-)。对于像我和你这样的普通人来说,使用“全局唯一”约束条件就不会发生了。如果您放弃该约束条件,则可以使用序列化器,因为它们是设计用于执行此操作的。 - atlaste
显示剩余4条评论
4个回答

14
你可以获取guid的字节组件,因此你可以直接对其进行处理:
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);
    }
}

编辑:现在字节顺序正确。


1
为什么会有踩票?能否解释一下这个答案的问题在哪里? - Thomas Levesque
当字节的值为255时,此代码在++操作中引发OverflowException异常。因此需要在该语句周围加上unchecked{}。 - Abacus
@Abacus,你的项目是否使用了/checked选项进行编译?默认情况下,整数算术运算是不受检查的。 - Thomas Levesque
是的,在我的项目中已经勾选了。或者说,它没有被取消勾选。我使用的是VS2010高级版,它在项目属性下的编译器选项中,进入高级设置,我将“移除整数溢出检查”保持未勾选状态。这似乎是一个明智的默认设置--如果默认行为是未勾选,那么对我来说就非常奇怪。 - Abacus

9

感谢Thomas Levesque提供的字节顺序,这里有一个漂亮的LINQ实现:

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]);
}

请注意,如果您成功地增加了它,它会环绕到 Guid.Empty
如果您持续增加单个 bytes 的副本而不是依次调用每个 GUID 上的 ToByteArray,那么效率会更高。

非常优雅。不过,你确定字节顺序正确吗? - Thomas Levesque
@ThomasLevesque 这与我看到的其他几个地方相符。 - Rawling
当字节的值为255时,此代码在++操作符处引发OverflowException异常。因此需要在该语句周围加上unchecked{}。 - Abacus
1
@Abacus 嗯,这取决于你是否在 checked 上下文中运行。VS 默认情况下不是,所以我没有费心。 - Rawling
1
我和我的编程搭档想要表扬一下.Any()函数真的很棒。非常酷。 - David Bond
显示剩余3条评论

1
可能的解决方案-我认为这个可以(没有真正测试过),但想要更好的解决方案。
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);
}

编辑:这是我最终使用的代码;感谢上面的答案提供了一般技术,但是如果没有"unchecked"子句,在某些情况下两者都会抛出异常。但我也试图尽可能使下面的内容易读。

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;
}

5
那个 if 语句链很棒。 - Rawling
2
使用 BigInteger 进行递增将会导致明显正确且可能看起来很好的代码。 - Alexei Levenkov

1

有序字符串的验证解决方案:

    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...");

    }

验证:
    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);
            }
        }

    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接