Ведет к base64, для URL
Вопрос: есть ли лучший способ сделать это?
VB.Net
Function GuidToBase64(ByVal guid As Guid) As String
Return Convert.ToBase64String(guid.ToByteArray).Replace("/", "-").Replace("+", "_").Replace("=", "")
End Function
Function Base64ToGuid(ByVal base64 As String) As Guid
Dim guid As Guid
base64 = base64.Replace("-", "/").Replace("_", "+") & "=="
Try
guid = New Guid(Convert.FromBase64String(base64))
Catch ex As Exception
Throw New Exception("Bad Base64 conversion to GUID", ex)
End Try
Return guid
End Function
C#
public string GuidToBase64(Guid guid)
{
return Convert.ToBase64String(guid.ToByteArray()).Replace("/", "-").Replace("+", "_").Replace("=", "");
}
public Guid Base64ToGuid(string base64)
{
Guid guid = default(Guid);
base64 = base64.Replace("-", "/").Replace("_", "+") + "==";
try {
guid = new Guid(Convert.FromBase64String(base64));
}
catch (Exception ex) {
throw new Exception("Bad Base64 conversion to GUID", ex);
}
return guid;
}
6 ответов
Я понимаю, что причина отсечения == в конце заключается в том, что вы можете быть уверены, что для GUID (из 16 байтов) закодированная строка всегда заканчивается ==. Таким образом, 2 символа могут быть сохранены в каждой конверсии.
Помимо уже упомянутой точки @Skurmedal (в случае ввода недопустимой строки следует выдать исключение), я думаю, что размещенный вами код достаточно хорош.
Вы можете проверить этот сайт: http://web.archive.org/web/20100408172352/http://prettycode.org/2009/11/12/short-guid/
Это выглядит очень близко к тому, что вы делаете.
public class ShortGuid
{
private readonly Guid guid;
private readonly string value;
/// <summary>Create a 22-character case-sensitive short GUID.</summary>
public ShortGuid(Guid guid)
{
if (guid == null)
{
throw new ArgumentNullException("guid");
}
this.guid = guid;
this.value = Convert.ToBase64String(guid.ToByteArray())
.Substring(0, 22)
.Replace("/", "_")
.Replace("+", "-");
}
/// <summary>Get the short GUID as a string.</summary>
public override string ToString()
{
return this.value;
}
/// <summary>Get the Guid object from which the short GUID was created.</summary>
public Guid ToGuid()
{
return this.guid;
}
/// <summary>Get a short GUID as a Guid object.</summary>
/// <exception cref="System.ArgumentNullException"></exception>
/// <exception cref="System.FormatException"></exception>
public static ShortGuid Parse(string shortGuid)
{
if (shortGuid == null)
{
throw new ArgumentNullException("shortGuid");
}
else if (shortGuid.Length != 22)
{
throw new FormatException("Input string was not in a correct format.");
}
return new ShortGuid(new Guid(Convert.FromBase64String
(shortGuid.Replace("_", "/").Replace("-", "+") + "==")));
}
public static implicit operator String(ShortGuid guid)
{
return guid.ToString();
}
public static implicit operator Guid(ShortGuid shortGuid)
{
return shortGuid.guid;
}
}
Одна из проблем, связанных с использованием этого метода для форматирования GUID для использования в URL-адресе или имени файла, заключается в том, что два разных GUID могут создавать два значения, которые отличаются только в случае, например:
var b1 = GuidToBase64(new Guid("c9d045f3-e21c-46d0-971d-b92ebc2ab83c"));
var b2 = GuidToBase64(new Guid("c9d045f3-e21c-46d0-971d-b92ebc2ab8a4"));
Console.WriteLine(b1); // 80XQyRzi0EaXHbkuvCq4PA
Console.WriteLine(b2); // 80XQyRzi0EaXHbkuvCq4pA
Поскольку URL-адреса и имена файлов часто интерпретируются как регистрозависимые, это может привести к коллизиям.
В.NET Core
вы можете использовать промежутки для повышения производительности и без выделения памяти.
using System.Buffers.Text;
using System.Runtime.InteropServices;
namespace Extensions;
public static class GuidExtensions
{
private const char Dash = '-';
private const char EqualsChar = '=';
private const byte ForwardSlashByte = (byte)Slash;
private const char Plus = '+';
private const byte PlusByte = (byte)Plus;
private const char Slash = '/';
private const char Underscore = '_';
private const int Base64LengthWithoutEquals = 22;
public static string EncodeBase64String(this Guid guid)
{
Span<byte> guidBytes = stackalloc byte[16];
Span<byte> encodedBytes = stackalloc byte[24];
MemoryMarshal.TryWrite(guidBytes, ref guid);
Base64.EncodeToUtf8(guidBytes, encodedBytes, out _, out _);
Span<char> chars = stackalloc char[Base64LengthWithoutEquals];
// Replace any characters which are not URL safe.
// And skip the final two bytes as these will be '==' padding we don't need.
for (int i = 0; i < Base64LengthWithoutEquals; i++)
{
chars[i] = encodedBytes[i] switch
{
ForwardSlashByte => Dash,
PlusByte => Underscore,
_ => (char)encodedBytes[i],
};
}
return new(chars);
}
public static Guid DecodeBase64String(this ReadOnlySpan<char> id)
{
Span<char> base64Chars = stackalloc char[24];
for (var i = 0; i < Base64LengthWithoutEquals; i++)
{
base64Chars[i] = id[i] switch
{
Dash => Slash,
Underscore => Plus,
_ => id[i],
};
}
base64Chars[22] = EqualsChar;
base64Chars[23] = EqualsChar;
Span<byte> idBytes = stackalloc byte[16];
Convert.TryFromBase64Chars(base64Chars, idBytes, out _);
return new(idBytes);
}
}
using AutoFixture.Xunit2;
using FluentAssertions;
using Extensions;
using Xunit;
namespace ExtensionTests;
public class GuidExtensionsTests
{
private const int Base64LengthWithoutEquals = 22;
private const string EmptyBase64 = "AAAAAAAAAAAAAAAAAAAAAA";
[Theory]
[AutoData]
public void EncodeBase64String_DecodeBase64String_Should_ReturnInitialGuid(Guid guid)
{
string actualBase64 = guid.EncodeBase64String();
actualBase64.Should().NotBe(string.Empty)
.And.HaveLength(Base64LengthWithoutEquals);
Guid actualGuid = ((ReadOnlySpan<char>)actualBase64).DecodeBase64String();
actualGuid.Should().Be(guid);
}
[Theory]
[InlineData(EmptyBase64)]
public void EncodeBase64String_Should_ReturnEmptyBase64_When_GuidIsEmpty(string expected)
{
string actualBase64 = Guid.Empty.EncodeBase64String();
actualBase64.Should().Be(expected);
}
[Theory]
[InlineData(EmptyBase64)]
public void DecodeBase64String_Should_ReturnEmptyGuid_When_StringIsEmptyBase64(string base64)
{
Guid actual = ((ReadOnlySpan<char>)base64).DecodeBase64String();
actual.Should().Be(Guid.Empty);
}
}
Для получения дополнительной информации прочитайте об использовании высокопроизводительных методов для кодирования base64 guid и очень хорошее видео-объяснение .
Если ваш метод не может преобразовать переданную ему Base64 в GUID, разве вы не должны вызвать исключение? Данные, переданные методу, явно ошибочны.
Существует метод, который я использую для кодирования, а также для сокращения моего URL-адреса (Guid): https://dotnetfiddle.net/iQ7nGv .
public static void Main()
{
Guid gg = Guid.NewGuid();
string ss = Encode(gg);
Console.WriteLine(gg);
Console.WriteLine(ss);
Console.WriteLine(Decode(ss));
}
public static string Encode(Guid guid)
{
string encoded = Convert.ToBase64String(guid.ToByteArray());
encoded = encoded.Replace("/", "_").Replace("+", "-");
return encoded.Substring(0, 22);
}
public static Guid Decode(string value)
{
value = value.Replace("_", "/").Replace("-", "+");
byte[] buffer = Convert.FromBase64String(value + "==");
return new Guid(buffer);
}