3

Guid is a 128bits structure, long is a Int64 so 64 bits structure, therefore Guid can be used to represent two long and two long can be stored in a Guid.

I have been searching several times for a reliable way to perform the transformation of a Guid to 2 longs and the way around, mainly to get a simple way to provide a tracking id to external services.

The objective is to get a reversable way to pass in a single parameter 2 longs, and decode it back later (of course it is not intended to be used "decoded" on the other side). It is like a session id for the external service.

Jean
  • 4,911
  • 3
  • 29
  • 50
  • A word of caution on treating `Guid` as anything else: there are two common ways of expressing guids, in terms of the internal endianness. If you expect compatibility with anything else: *check carefully* – Marc Gravell Mar 19 '18 at 22:01
  • If `unsafe` is an option, this can be about 3 lines of code :) – Marc Gravell Mar 19 '18 at 22:01
  • Yes, consider adding a check of `BitConverter.IsLittleEndian` to one of the answers provided to determine if the bytes should be reversed if you need to ensure that the bytes are always in the same order regardless of the platform. – BlueMonkMN Mar 19 '18 at 22:38
  • @MarcGravell I have updated the question, as it does not intend to be reversed externally, but I am still interested in your unsafe suggestion ;) – Jean Mar 20 '18 at 01:18
  • @BlueMonkMN I would agree on your suggestion if the objective was to have interoperability, which is not my case, so I prefer a faster solution. However it would also require to be sure on how the guid is stored in order to check the Endian, which I am not so sure of... – Jean Mar 20 '18 at 01:42
  • @Jean done per request :) – Marc Gravell Mar 20 '18 at 09:34
  • @BlueMonkMN while that's true, I was actually talking about the layout of bytes *within* the guid between different systems (https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding), but yes - you'd need to check the CPU endianness too, if using byte thunking – Marc Gravell Mar 20 '18 at 09:37

4 Answers4

5

Warning: these solutions do not take endianness into consideration, and the result may therefore differ from one platform to another

Taking advantage of new features of C# 7, I came out with the following tools class, which transforms long, ulong, int, uint to Guid and reverse:

public static class GuidTools
{
    public static Guid GuidFromLongs(long a, long b)
    {
        byte[] guidData = new byte[16];
        Array.Copy(BitConverter.GetBytes(a), guidData, 8);
        Array.Copy(BitConverter.GetBytes(b), 0, guidData, 8, 8);
        return new Guid(guidData);
    }

    public static (long, long) ToLongs(this Guid guid)
    {
        var bytes = guid.ToByteArray();
        var long1 = BitConverter.ToInt64(bytes, 0);
        var long2 = BitConverter.ToInt64(bytes, 8);
        return (long1, long2);
    }

    public static Guid GuidFromULongs(ulong a, ulong b)
    {
        byte[] guidData = new byte[16];
        Array.Copy(BitConverter.GetBytes(a), guidData, 8);
        Array.Copy(BitConverter.GetBytes(b), 0, guidData, 8, 8);
        return new Guid(guidData);
    }

    public static (ulong, ulong) ToULongs(this Guid guid)
    {
        var bytes = guid.ToByteArray();
        var ulong1 = BitConverter.ToUInt64(bytes, 0);
        var ulong2 = BitConverter.ToUInt64(bytes, 8);
        return (ulong1, ulong2);
    }

    public static Guid GuidFromInts(int a, int b, int c, int d)
    {
        byte[] guidData = new byte[16];
        Array.Copy(BitConverter.GetBytes(a), guidData, 4);
        Array.Copy(BitConverter.GetBytes(b), 0, guidData, 4, 4);
        Array.Copy(BitConverter.GetBytes(c), 0, guidData, 8, 4);
        Array.Copy(BitConverter.GetBytes(d), 0, guidData, 12, 4);
        return new Guid(guidData);
    }

    public static (int, int , int, int) ToInts(this Guid guid)
    {
        var bytes = guid.ToByteArray();
        var a = BitConverter.ToInt32(bytes, 0);
        var b = BitConverter.ToInt32(bytes, 4);
        var c = BitConverter.ToInt32(bytes, 8);
        var d = BitConverter.ToInt32(bytes, 12);
        return (a, b, c, d);
    }

    public static Guid GuidFromUInts(uint a, uint b, uint c, uint d)
    {
        byte[] guidData = new byte[16];
        Array.Copy(BitConverter.GetBytes(a), guidData, 4);
        Array.Copy(BitConverter.GetBytes(b), 0, guidData, 4, 4);
        Array.Copy(BitConverter.GetBytes(c), 0, guidData, 8, 4);
        Array.Copy(BitConverter.GetBytes(d), 0, guidData, 12, 4);
        return new Guid(guidData);
    }

    public static (uint, uint, uint, uint) ToUInts(this Guid guid)
    {
        var bytes = guid.ToByteArray();
        var a = BitConverter.ToUInt32(bytes, 0);
        var b = BitConverter.ToUInt32(bytes, 4);
        var c = BitConverter.ToUInt32(bytes, 8);
        var d = BitConverter.ToUInt32(bytes, 12);
        return (a, b, c, d);
    }
}

Also found another solution inspired from there: Converting System.Decimal to System.Guid

[StructLayout(LayoutKind.Explicit)]
struct GuidConverter
{
    [FieldOffset(0)]
    public decimal Decimal;
    [FieldOffset(0)]
    public Guid Guid;
    [FieldOffset(0)]
    public long Long1;
    [FieldOffset(8)]
    public long Long2;
}

private static GuidConverter _converter;
public static (long, long) FastGuidToLongs(this Guid guid)
{
    _converter.Guid = guid;
    return (_converter.Long1, _converter.Long2);
}
public static Guid FastLongsToGuid(long a, long b)
{
    _converter.Long1 = a;
    _converter.Long2 = b;
    return _converter.Guid;
}
Jean
  • 4,911
  • 3
  • 29
  • 50
3

As an unsafe but very efficient version (no byte[] allocations, via BitConverter):

static void Main()
{
    var g = Guid.NewGuid();
    Console.WriteLine(g);
    GuidToInt64(g, out var x, out var y);
    Console.WriteLine(x);
    Console.WriteLine(y);
    var g2 = GuidFromInt64(x, y);
    Console.WriteLine(g2);
}
public static unsafe void GuidToInt64(Guid value, out long x, out long y)
{
    long* ptr = (long*)&value;
    x = *ptr++;
    y = *ptr;
}
public static unsafe Guid GuidFromInt64(long x, long y)
{
    long* ptr = stackalloc long[2];
    ptr[0] = x;
    ptr[1] = y;
    return *(Guid*)ptr;
}

You could actually do the same thing with a union struct, if you don't like using the unsafe keyword, but: it is more code, and a union struct is still fundamentally unverifiable, so this doesn't gain you much at the IL level (it just means you don't need the "allow unsafe code" flag):

static void Main()
{
    var g = Guid.NewGuid();
    Console.WriteLine(g);

    var val = new GuidInt64(g);
    var x = val.X;
    var y = val.Y;
    Console.WriteLine(x);
    Console.WriteLine(y);

    var val2 = new GuidInt64(x, y);
    var g2 = val2.Guid;
    Console.WriteLine(g2);
}

[StructLayout(LayoutKind.Explicit)]
struct GuidInt64
{
    [FieldOffset(0)]
    private Guid _guid;
    [FieldOffset(0)]
    private long _x;
    [FieldOffset(8)]
    private long _y;

    public Guid Guid => _guid;
    public long X => _x;
    public long Y => _y;

    public GuidInt64(Guid guid)
    {
        _x = _y = 0; // to make the compiler happy
        _guid = guid;
    }
    public GuidInt64(long x, long y)
    {
        _guid = Guid.Empty;// to make the compiler happy
        _x = x;
        _y = y;
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
0

Following pair of methods could do what you need:

    public static void GuidToInt16(Guid guidToConvert, out long guidAsLong1, out long guidAsLong2)
    {
        byte[] guidByteArray = guidToConvert.ToByteArray();
        var segment1 = new ArraySegment<byte>(guidByteArray, 0, 8);
        var segment2 = new ArraySegment<byte>(guidByteArray, 8, 8);
        guidAsLong1 = BitConverter.ToInt64(segment1.ToArray(), 0);
        guidAsLong2 = BitConverter.ToInt64(segment2.ToArray(), 0);
    }

    public static Guid Int16ToGuid(long guidAsLong1, long guidAsLong2)
    {
        var segment1 = BitConverter.GetBytes(guidAsLong1);
        var segment2 = BitConverter.GetBytes(guidAsLong2);
        return new Guid(segment1.Concat(segment2).ToArray());
    }

And possible usage:

        Guid guidToConvert = new Guid("cbd5bb87-a249-49ac-8b06-87c124205b99");
        long guidAsLong1, guidAsLong2;

        GuidToInt16(guidToConvert, out guidAsLong1, out guidAsLong2);
        Console.WriteLine(guidAsLong1 + " " + guidAsLong2);

        Guid guidConvertedBack = Int16ToGuid(guidAsLong1, guidAsLong2);
        Console.WriteLine(guidConvertedBack);

        Console.ReadKey();
Ivan Golović
  • 8,732
  • 3
  • 25
  • 31
0

My solution should help understand whole process with binary operations:

    class Program
{
    public static Guid LongsToGuid(long l1, long l2)
    {
        var a = (int)l1;
        var b = (short)(l1 >> 32);
        var c = (short)(l1 >> 48);

        var d = (byte)l2;
        var e = (byte)(l2 >> 8);
        var f = (byte)(l2 >> 16);
        var g = (byte)(l2 >> 24);
        var h = (byte)(l2 >> 32);
        var i = (byte)(l2 >> 40);
        var j = (byte)(l2 >> 48);
        var k = (byte)(l2 >> 56);

        return new Guid(a, b, c, d, e, f, g, h, i, j, k);
    }

    public static long BytesToLong(byte[] bytes, int start, int end)
    {
        long toReturn = 0;

        for (var i = start; i < end; i++)
        {
            toReturn |= ((long)bytes[i]) << (8 * i);
        }

        return toReturn;
    }

    static void Main(string[] args)
    {
        var l1 = long.MinValue;
        var l2 = long.MaxValue;

        var guid = LongsToGuid(l1, l2);
        var guidBytes = guid.ToByteArray();

        var readL1 = BytesToLong(guidBytes, 0, 8);
        var readL2 = BytesToLong(guidBytes, 8, 16);

        Console.WriteLine(l1 == readL1);
        Console.WriteLine(l2 == readL2);

        Console.ReadKey();
    }
}
lbarczynski
  • 170
  • 11