Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API Proposal]: Guid from int128 and Guid to int128 #73781

Closed
jhudsoncedaron opened this issue Aug 11, 2022 · 10 comments
Closed

[API Proposal]: Guid from int128 and Guid to int128 #73781

jhudsoncedaron opened this issue Aug 11, 2022 · 10 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Runtime
Milestone

Comments

@jhudsoncedaron
Copy link

Background and motivation

We have new type Int128 that is the same size as a Guid. I'd pretty much like to use it everywhere I have byte[16]; and just not allocate the arrays for dealing with Guid.

public partial struct Guid {
   public Guid(int128 b);
   public Int128 ToInt128();
}

I'd really like the int128 values to exactly match the in-memory values of the bytes in the byte[16] array.

Or put another way, the following (bad) way to write the constructor: public Guid(int128 b) : this(new ReadOnlySpan<byte>((byte*)&b, 16)) {}

API Proposal

namespace System.Collections.Generic;

public class MyFancyCollection<T> : IEnumerable<T>
{
    public void Fancy(T item);
}

API Usage

// Fancy the value
var c = new MyFancyCollection<int>();
c.Fancy(42);

// Getting the values out
foreach (var v in c)
    Console.WriteLine(v);

Alternative Designs

While I did come up with an alternative design, it immortalizes the bad way of writing the constructor.

An even worse idea is to to the endian bitbashing and reinterpret cast between guid and int128.

Risks

A lot less risky than reinterpret casts lying around.

@jhudsoncedaron jhudsoncedaron added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Aug 11, 2022
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Aug 11, 2022
@teo-tsirpanis
Copy link
Contributor

I don't think this is a good idea. Int128 represents an integer that can be used in mathematic operations. Guid represents an opaque identifier. These two are fundamentally different types and there is not a single canonical way to convert between them. If my guess that you want to store them in a database is correct, I would recommend you to store them either as a number if your DBMS supports higher sizes, or as a BLOB.

@jhudsoncedaron
Copy link
Author

@teo-tsirpanis : It has more to do with eliminating allocations than anything else.

@teo-tsirpanis
Copy link
Contributor

Yeah I imagine. You should reach out to the maintainers of the DBMS and its drivers. Maybe there could be a new API in ADO.NET but I don't know how it would look like.

@jhudsoncedaron
Copy link
Author

@teo-tsirpanis : You imagine badly. I am working on the logical equivalent of being the DBMS driver.

@teo-tsirpanis
Copy link
Contributor

Something you could do is write the Int128 to a span of 16 bytes and then read the GUID from them, and vice versa. Both types define methods to read and write from byte arrays (you need to wait until the RC1 for the relevant APIs on IBinaryInteger).

It is more predictable with regards to endianness and avoids reinterpret casts.

@ghost
Copy link

ghost commented Aug 11, 2022

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and motivation

We have new type Int128 that is the same size as a Guid. I'd pretty much like to use it everywhere I have byte[16]; and just not allocate the arrays for dealing with Guid.

public partial struct Guid {
   public Guid(int128 b);
   public Int128 ToInt128();
}

I'd really like the int128 values to exactly match the in-memory values of the bytes in the byte[16] array.

Or put another way, the following (bad) way to write the constructor: public Guid(int128 b) : this(new ReadOnlySpan<byte>((byte*)&b, 16)) {}

API Proposal

namespace System.Collections.Generic;

public class MyFancyCollection<T> : IEnumerable<T>
{
    public void Fancy(T item);
}

API Usage

// Fancy the value
var c = new MyFancyCollection<int>();
c.Fancy(42);

// Getting the values out
foreach (var v in c)
    Console.WriteLine(v);

Alternative Designs

While I did come up with an alternative design, it immortalizes the bad way of writing the constructor.

An even worse idea is to to the endian bitbashing and reinterpret cast between guid and int128.

Risks

A lot less risky than reinterpret casts lying around.

Author: jhudsoncedaron
Assignees: -
Labels:

api-suggestion, area-System.Runtime, untriaged

Milestone: -

@Clockwork-Muse
Copy link
Contributor

I would recommend you to store them either as a number if your DBMS supports higher sizes, or as a BLOB.

Most RDBMSs have a GUID type, though.

@huoyaoyuan
Copy link
Member

The byte order of a GUID is not clearly defined. See also #53354.

@carlossanlop carlossanlop added this to the Future milestone Aug 12, 2022
@carlossanlop carlossanlop removed the untriaged New issue has not been triaged by the area owner label Aug 12, 2022
@tannergooding
Copy link
Member

While Guid and Int128 are the same size, they are not compatible and conversion between them is not a "normal" operation.

A valid "Guid" definition could be one of many ranging from a single 128-bit value, to 16x uint8, to 2x uint64, to the layout that we currently have .NET (which is modeled after Windows, being 1x uint32, 2x uint16, 8x uint8).

Given the current layout, a "correct" conversion would likely be one that is equivalent to:

Span<byte> tmp = stackalloc byte[16];

bool succeeded = guid.TryWriteBytes(tmp);
Debug.Assert(succeeded);

if (BitConverter.IsLittleEndian)
{
    return BinaryPrimitives.ReadInt128LittleEndian(tmp);
}
else
{
    return BinaryPrimitives.ReadInt128BigEndian(tmp);
}

This is different from a simple "reinterpret-cast" which is much cheaper and just gives the raw bits exactly as is. Which of these two operations you want is likely entirely dependent on your own scenario.

For the "reinterpret-cast" scenario, I wouldn't be opposed to a proposal that asks for BitConverter.GuidToInt128Bits and BitConverter.Int128BitsToGuid. These match the existing float <-> int and double <-> long bitcast operations we already expose. However, its worth noting that this may overall be a niche enough scenario that simply defining your own such API is more than sufficient.

For the "correctly convert the Guid layout to a Int128 integer" scenario, the relevant APIs to do this are mostly all present. What's missing is the BinaryPrimitives APIs, but there is a proposal to make those available. I don't think exposing APIs on Guid or Int128 directly is the right thing here.

@teo-tsirpanis teo-tsirpanis closed this as not planned Won't fix, can't repro, duplicate, stale Aug 13, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Sep 12, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Runtime
Projects
None yet
Development

No branches or pull requests

7 participants