我有一个不寻常的情况,我有一个使用二进制(16)主键的现有 MySQL 数据库,这些是现有 api 中使用的 UUID 的基础。
我的问题是,我现在想添加一个用 dotnet core 编写的替换 api,并且我遇到了编码问题,已在 here
进行了解释具体来说,dotnet 中的 Guid 结构使用混合字节序格式,生成与现有 api 不同的字符串。由于显而易见的原因,这是不可接受的。
所以我的问题是:有没有一种优雅的方法来强制 Guid 结构完全使用大端格式进行编码?
如果没有,我可以写一个糟糕的 hack,但我想我应该首先检查 SO 社区的集体智慧!
不;据我所知,没有内置的方法可以实现这一点。是的,
Guid
目前只能称之为“疯狂字节序”实现。您需要获取 Guid
排序的位(通过 unsafe
或 Guid.ToByteArray
),然后手动排序它们,找出要反转的块 - 这不是一个简单的 Array.Reverse()
。所以:恐怕非常手动。我建议使用类似的指南
00010203-0405-0607-0809-0a0b0c0d0e0f
调试它;这给了你(我怀疑你知道):
03-02-01-00-05-04-07-06-08-09-0A-0B-0C-0D-0E-0F
所以:
截至 2021 年 2023 年,仍然没有内置方法可以在 C# 中将 System.Guid
转换为 MySQL 兼容的大端字符串。这是我们在工作中遇到这个确切的 C# 混合字节序 Guid 问题时提出的扩展:
原版
public static string ToStringBigEndian(this Guid guid)
{
// allocate enough bytes to store Guid ASCII string
Span<byte> result = stackalloc byte[36];
// set all bytes to 0xFF (to be able to distinguish them from real data)
result.Fill(0xFF);
// get bytes from guid
Span<byte> buffer = stackalloc byte[16];
_ = guid.TryWriteBytes(buffer);
int skip = 0;
// iterate over guid bytes
for (int i = 0; i < buffer.Length; i++)
{
// indices 4, 6, 8 and 10 will contain a '-' delimiter character in the Guid string.
// --> leave space for those delimiters
if (i is 4 or 6 or 8 or 10)
{
skip++;
}
// stretch high and low bytes of every single byte into two bytes (skipping '-' delimiter characters)
result[(2 * i) + skip] = (byte)(buffer[i] >> 0x4);
result[(2 * i) + 1 + skip] = (byte)(buffer[i] & 0x0Fu);
}
// iterate over precomputed byte array.
// values 0x0 to 0xF are final hex values, but must be mapped to ASCII characters.
// value 0xFF is to be mapped to '-' delimiter character.
for (int i = 0; i < result.Length; i++)
{
// map bytes to ASCII values (a-f will be lowercase)
ref byte b = ref result[i];
b = b switch
{
0xFF => 0x2D, // Map 0xFF to '-' character
< 0xA => (byte)(b + 0x30u), // Map 0x0 - 0x9 to '0' - '9'
_ => (byte)(b + 0x57u) // Map 0xA - 0xF to 'a' - 'f'
};
}
// get string from ASCII encoded guid byte array
return Encoding.ASCII.GetString(result);
}
它有点长,但除了它返回的大端字符串之外,它不进行堆分配,因此保证速度很快:)2023 年更新:更快的版本
public static string ToStringBigEndian(this Guid guid)
{
// allocate enough bytes to store Guid ASCII string
Span<byte> result = stackalloc byte[36];
// get bytes from guid
Span<byte> buffer = stackalloc byte[16];
_ = guid.TryWriteBytes(buffer);
int skip = 0;
// iterate over guid bytes
for (int i = 0; i < buffer.Length; i++)
{
// indices 4, 6, 8 and 10 will contain a '-' delimiter character in the Guid string.
// --> leave space for those delimiters
// we can check if i is even and i / 2 is >= 2 and <= 5 to determine if we are at one of those indices
// 0xF...F if i is odd and 0x0...0 if i is even
int isOddMask = -(i & 1);
// 0xF...F if i / 2 is < 2 and 0x0...0 if i / 2 is >= 2
int less2Mask = ((i >> 1) - 2) >> 31;
// 0xF...F if i / 2 is > 5 and 0x0...0 if i / 2 is <= 5
int greater5Mask = ~(((i >> 1) - 6) >> 31);
// 0xF...F if i is even and 2 <= i / 2 <= 5 otherwise 0x0...0
int skipIndexMask = ~(isOddMask | less2Mask | greater5Mask);
// skipIndexMask will be 0xFFFFFFFF for indices 4, 6, 8 and 10 and 0x00000000 for all other indices
// --> skip those indices
skip += 1 & skipIndexMask;
result[(2 * i) + skip] = ToHexCharBranchless(buffer[i] >>> 0x4);
result[(2 * i) + skip + 1] = ToHexCharBranchless(buffer[i] & 0x0F);
}
// add dashes
const byte dash = (byte)'-';
result[8] = result[13] = result[18] = result[23] = dash;
// get string from ASCII encoded guid byte array
return Encoding.ASCII.GetString(result);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte ToHexCharBranchless(int b) =>
// b + 0x30 for [0-9] if 0 <= b <= 9 and b + 0x30 + 0x27 for [a-f] if 10 <= b <= 15
(byte)(b + 0x30 + (0x27 & ~((b - 0xA) >> 31)));
基准测试结果表明性能提高了约 30%:
为什么 Guid.ToByteArray() 以这种方式对字节进行排序?
using System;
namespace rm.Extensions;
/// <summary>
/// Guid extensions.
/// </summary>
public static class GuidExtension
{
/// <summary>
/// Returns a 16-element byte array that contains the value of this instance
/// matching its string representation (endian-agnostic).
/// <para></para>
/// See https://stackoverflow.com/questions/9195551/why-does-guid-tobytearray-order-the-bytes-the-way-it-does
/// <remarks>
/// <para></para>
/// Note: The byte[] returned by <see cref="ToByteArrayMatchingStringRepresentation(Guid)"/> will not yield
/// the original Guid with <see cref="Guid(byte[])"/> ctor.
/// </remarks>
/// </summary>
public static byte[] ToByteArrayMatchingStringRepresentation(this Guid guid)
{
var bytes = guid.ToByteArray();
TweakOrderOfGuidBytesToMatchStringRepresentation(bytes);
return bytes;
}
/// <summary>
/// Initializes a new instance of the <see cref="Guid"></see> structure by using the specified array of bytes
/// matching its string representation (endian-agnostic).
/// <para></para>
/// See https://stackoverflow.com/questions/9195551/why-does-guid-tobytearray-order-the-bytes-the-way-it-does
/// <remarks>
/// <para></para>
/// Note: The Guid returned by <see cref="ToGuidMatchingStringRepresentation(byte[])"/> will not yield
/// the original byte[] with <see cref="Guid.ToByteArray()"/>.
/// </remarks>
/// </summary>
public static Guid ToGuidMatchingStringRepresentation(this byte[] bytes)
{
_ = bytes ??
throw new ArgumentNullException(nameof(bytes));
if (bytes.Length != 16)
{
throw new ArgumentException("Length should be 16.", nameof(bytes));
}
TweakOrderOfGuidBytesToMatchStringRepresentation(bytes);
return new Guid(bytes);
}
private static void TweakOrderOfGuidBytesToMatchStringRepresentation(byte[] guidBytes)
{
if (BitConverter.IsLittleEndian)
{
Array.Reverse(guidBytes, 0, 4);
Array.Reverse(guidBytes, 4, 2);
Array.Reverse(guidBytes, 6, 2);
}
}
}
来源:https://github.com/rmandvikar/csharp-extensions/blob/dev/src/rm.Extensions/GuidExtension.cs