我想使用一些自定义的格式化参数将byte[]
和ReadOnlySpan<byte>
字节格式化为字符串。比如,将S
化为Base64
。为此,长度总是固定为某个已知的常量。
我想使用https://devblogs.microsoft.com/dotnet/string-interpolation-in-c-10-and-net-6/中描述的现代C# 10和.NET 6字符串格式化功能。内置类型实现了ISpanFormattable,因此我想在这里引入新的格式化参数,但使用了编译器handler lowering pattern。
我从那篇文章中提取了一些代码,并在嵌入的代码中做了一些修改,如下所示。它也位于https://dotnetfiddle.net/svyQKD。
如代码中所示,我获得了byte[]
的直接方法调用,但ReadOnlySpan<byte>
的调用没有成功。
有人知道怎么做吗?
我怀疑我需要InterpolatedStringHandler。但如果是这样的话,那么看起来我不知道如何实现一个。所有的提示和代码技巧可能会有帮助。我已经在这个问题上停留了一段时间,现在已经进入凌晨。:)
using System;
using System.Globalization;
using System.Runtime.CompilerServices;
public class Program
{
public sealed class ExampleCustomFormatter: IFormatProvider, ICustomFormatter
{
public object? GetFormat(Type? formatType) => formatType == typeof(ICustomFormatter) ? this : null;
public string Format(string? format, object? arg, IFormatProvider? formatProvider) => format == "S" && arg is byte[] i ? Convert.ToBase64String(i) : arg is IFormattable formattable ? formattable.ToString(format, formatProvider) : arg?.ToString() ?? string.Empty;
}
public static class StringExtensions
{
public static string FormatString(byte[] buffer) => string.Create(new ExampleCustomFormatter(), stackalloc char[64], $"{buffer:S}");
// How to make this work? Maybe needs to have TryWrite
// public static string FormatString2(ReadOnlySpan<byte> buffer) => string.Create(new ExampleCustomFormatter(), stackalloc char[64], $"{buffer:S}");
}
[InterpolatedStringHandler]
public ref struct BinaryMessageInterpolatedStringHandler
{
private readonly DefaultInterpolatedStringHandler handler;
public BinaryMessageInterpolatedStringHandler(int literalLength, int formattedCount, bool predicate, out bool handlerIsValid)
{
handler = default;
if(predicate)
{
handlerIsValid = false;
return;
}
handlerIsValid = true;
handler = new DefaultInterpolatedStringHandler(literalLength, formattedCount);
}
public void AppendLiteral(string s) => handler.AppendLiteral(s);
public void AppendFormatted<T>(T t) => handler.AppendFormatted(t);
public override string ToString() => handler.ToStringAndClear();
}
public static void Main()
{
byte[] test1 = new byte[1] { 0x55 };
ReadOnlySpan<byte> test2 = new byte[1] { 0x55 };
// How to make this work? Now it prints "System.Byte[]".
Console.WriteLine($"{test1:S}");
// This works.
Console.WriteLine(StringExtensions.FormatString(test1));
// How to make this work? This does not compile. (Yes, signature problem. How to define it?).
// Console.WriteLine($"{test2:S}");
// How to make this work? This does not compile. (Yes, signature problem. How to define it?).
// Console.WriteLine(StringExtensions.FormatString(test2));
}
}
3条答案
按热度按时间oxiaedzo1#
我玩了这个,因为我也有兴趣了解这些处理程序,所以考虑我的答案与一些怀疑。
Console.WriteLine($"{test2:S}");
(和StringExtensions.FormatString2
)上的构建错误CS0306 The type 'ReadOnlySpan<byte>' may not be used as a type argument
源于(我相信)编译器生成的对DefaultInterpolatedStringHandler.AppendFormatted<T>
的调用,该调用带有非法的泛型类型参数ReadOnlySpan<byte>
(ReadOnlySpan
是ref struct
,ref struct
不能用作类型参数)。正如您链接的Stephen Toub的博客中所解释的,编译器会很乐意扩展任何插值字符串以使用
DefaultInterpolatedStringHandler
(如果它愿意的话),这就是为什么您可以使用带有Console.WriteLine
的插值字符串,即使它没有带DefaultInterpolatedStringHandler
参数的重载:一个简单的解决方案是创建一个
DefaultInterpolatedStringHandler.AppendFormatted
的扩展重载,它接受一个ReadOnlySpan<byte>
,但是在这个残酷的世界里,编译器似乎没有检测到它:所以这里有一个解决方案:用
AppendFormatted
重载创建一个自定义的InterpolatedStringHandler
(就像你已经做的那样)。就像在你的实现中一样,我们可以只 Package 一个DefaultInterpolatedStringHandler
(或者一个StringBuilder
),这样实际的实现(缓冲区管理等)就留给更聪明的人了。完整的例子:我们新的
AppendFormatted
重载调用Convert.ToBase64String
,后者正在分配中间string
(然后将其复制到DefaultInterpolatedStringHandler
的缓冲区中)。这是我们为自己没有管理缓冲区而付出的代价。如果我们不能忍受这样的想法(让实现风险和收益递减见鬼去吧),这里有一个哑处理程序,它将(固定大小)缓冲区作为参数(可以进行堆栈分配):总之,只需调用
Console.WriteLine(Convert.ToBase64String(test2))
,然后继续:)ac1kyiln2#
您可以使用延伸方法:
结果:
您也可以尝试使用:
gupuwyp23#
如果您真的想像这样使用方法,则需要重写
Byte[]
类的ToString()
方法。但是您无法在
Byte[]
类别上覆写方法。您必须继承Byte[]
类别,并在衍生的上覆写ToString()
方法。然后,您必须使用衍生类别取代所有的
Byte[]
对象,使用并不是一个好主意。因此,没有解决方案为您在这种方式:
最好的办法是创建一个“outside”方法来格式化
Byte[]
,然后按照格式化的方法进行操作。ReadOnlySpan<byte>
。