2

我偶然发现了自定义 IComparer 实现的一些奇怪行为,似乎无法弄清楚如何纠正它并获得预期的行为。

我创建了一个静态类,为 System.Guid 提供了一个扩展方法,它允许用指定的 Int32 值覆盖 Guid 的前 4 个字节。这样做是为了允许创建对高人口数据库表索引友好的半顺序 Guid。

public static class GuidExt
{
    public static Guid Sequence(this Guid obj, int index)
    {
        byte[] b = obj.ToByteArray();
        BitConverter.GetBytes(index).CopyTo(b, 0);
        return new Guid(b);
    }
}

非常简单,并且完全按预期工作。

我创建的自定义 Comparer 类旨在允许 Guid 按 Guid 的插入 Int32 部分的升序排序。实现如下:

public class GuidSequenceComparer : IComparer<Guid>
{
    public int Compare(Guid x, Guid y)
    {
        var xBytes = x.ToByteArray();
        var yBytes = y.ToByteArray();
        byte[] xIndexBytes = new byte[4];
        for (int i = 0; i < 4; i++)
        {
            xIndexBytes[i] = xBytes[0];
        }
        byte[] yIndexBytes = new byte[4];
        for (int i = 0; i < 4; i++)
        {
            yIndexBytes[i] = yBytes[i];
        }
        var xIndex = BitConverter.ToInt32(xIndexBytes, 0);
        var yIndex = BitConverter.ToInt32(yIndexBytes, 0);

        return xIndex.CompareTo(yIndex);

        //// The following was used to test if any sort was being performed
        //// and reverses the ordering (see below paragraph)
        // if (xIndex > yIndex)
        // {
        //     return -1;
        // }
        // if (xIndex < yIndex)
        // {
        //     return 1
        // }
        // return 0;
    }
}

当我在 List 上使用这个自定义比较器时,它会执行排序,但它会对 List 对象中的索引位置进行排序!不知道为什么会发生这种情况,但我通过反转比较结果来确认它,如注释掉的部分所示,它只是反转了列表的顺序,不是基于 Guid 中的 int 值,而是基于现有位置列表内。我完全不知道为什么会这样。

如果您想尝试重新创建行为,这是我在控制台应用程序中使用的简单测试。

        List<Guid> guidList = new List<Guid>();

        guidList.Add(Guid.NewGuid().Sequence(5));
        guidList.Add(Guid.NewGuid().Sequence(3));
        guidList.Add(Guid.NewGuid().Sequence(8));
        guidList.Add(Guid.NewGuid().Sequence(1));

        Console.WriteLine("unsorted:");
        foreach (Guid item in guidList)
        {
            Console.WriteLine(item);
        }

        guidList.Sort(new GuidSequenceComparer());

        Console.WriteLine("sorted:");
        foreach (Guid item in guidList)
        {
            Console.WriteLine(item);
        }

        Console.ReadLine();
4

3 回答 3

3

我会优化你的比较

public int Compare(Guid x, Guid y)
{
    var xBytes = x.ToByteArray();
    var yBytes = y.ToByteArray();
    int result = 0;
    for (int i = 0; i < 4; i++)
    {
        var result = xBytes[i].CompareTo(yBytes[i]);
        if (result != 0)
        {
            break;
        }
    }

    return result;
}

Buffer.BlockCopyArray.Copy太快一点,

public static Guid Sequence(this Guid source, int index)
{
    var buffer = source.ToByteArray();
    Buffer.BlockCopy(BitConvertor.GetBytes(index), 0, buffer, 0, sizeof(int));
    return new Guid(buffer);
}

或者这个怎​​么样,不需要复制

public static Guid Sequence(this Guid source, int index)
{
    var buffer = source.ToByteArray();
    return new Guid(
        index,
        BitConvertor.ToInt16(buffer, 4),
        BitConvertor.ToInt16(buffer, 6),
        buffer[8],
        buffer[9],
        buffer[10],
        buffer[11],
        buffer[12],
        buffer[13],
        buffer[14],
        buffer[15]);
}
于 2013-04-30T13:14:28.087 回答
3

您可能需要考虑将扩展方法调整为:

public static class GuidExt
{
    public static Guid Sequence(this Guid g, int sequenceNum)
    {
        var bytes = g.ToByteArray();

        BitConverter.GetBytes(sequenceNum).CopyTo(bytes, 0);

        return new Guid(bytes);
    }

    public static int GetSequenceNum(this Guid g)
    {
        return BitConverter.ToInt32(g.ToByteArray(), 0);
    }
}

您的方法中有相当多的额外回转,您根本不需要。有趣的是,这就是 Jalayn 发现的错误的根源。

然后你可以改变你的比较器来做一些更简单的事情:

public int Compare(Guid x, Guid y)
{
    return x.GetSequenceNum().CompareTo(y.GetSequenceNum());
}

希望这可以帮助。

于 2013-04-30T13:16:44.513 回答
3

在你的比较中,我认为这条线

        xIndexBytes[i] = xBytes[0];

应该

        xIndexBytes[i] = xBytes[i];

不确定是否是问题所在,但这正是我不时发生的事情:-)

用户@Magnus 击败了我,但您可以使用该Array.Copy函数避免复制循环,如下所示:

Array.Copy(xBytes, 0, xIndexBytes, 0, 4);
于 2013-04-30T13:04:54.563 回答