自定义IComparer实现由于某种原因未按预期排序

时间:2013-04-30 13:00:35

标签: c# .net generics

我偶然发现了自定义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对象中的索引位置进行排序!不知道为什么会这样,但我通过反转比较结果证实了这一点,如注释部分所示,它只是颠倒了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();

3 个答案:

答案 0 :(得分:3)

在您的比较器中,我认为该行

        xIndexBytes[i] = xBytes[0];

应该是

        xIndexBytes[i] = xBytes[i];

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

用户@Magnus打败了我,但你可以避免使用Array.Copy功能的复制循环,如下所示:

Array.Copy(xBytes, 0, xIndexBytes, 0, 4);

答案 1 :(得分: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]);
}

答案 2 :(得分: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());
}

希望这有帮助。