使用 C# 10 和 .NET 6 字符串插值、编译器处理程序降低模式​​将 byte[] 或 ReadOnlySpan<byte> 格式化为字符串

Formatting byte[] or ReadOnlySpan<byte> to string using C# 10 and .NET 6 string interpolation, compiler handler lowering pattern

我想使用一些自定义格式化参数将 byte[]ReadOnlySpan<byte> 字节格式化为字符串。比如说 S 代表 Base64。为此,长度始终固定为某个已知常数。

我想使用 https://devblogs.microsoft.com/dotnet/string-interpolation-in-c-10-and-net-6/. The built-in types implement ISpanFormattable and so what I'd like to bring here is new formatting parameters but so that compiler handler lowering pattern 中描述的现代 C# 10 和 .NET 6 字符串格式化功能。

我从 post 中提取了一些代码,并在嵌入的代码中对其进行了一些修改,如下所示。它也在 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));
    }
}

如果你真的想使用这样的方法,你需要覆盖classByte[]ToString()方法。

但是您不能在 class Byte[] 上覆盖该方法。您需要继承 class Byte[] 并覆盖派生的 ToString() 方法。

然后,您必须用派生的 class 替换所有 Byte[] 对象,这不是一个好主意。

所以,没有适合您的解决方案:

// How to make this work? Now it prints "System.Byte[]".
Console.WriteLine($"{test1:S}");

你能做的最好的事情是创建一个“外部”方法来格式化 Byte[] 并按照你的方式进行格式化。

*ReadOnlySpan<byte>.

同理

您可以使用扩展方法:

using System.Text;

byte[] test1 = new byte[2] { 0x55, 0x34 };
ReadOnlySpan<byte> test2 = new byte[2] { 0x55, 0x34 };

// How to make this work? Now it prints "System.Byte[]".
Console.WriteLine($"{test1.MyFormat()}");

Console.WriteLine($"{test2.MyFormat()}");

public static class MyExtensionMethods
{
    public static string MyFormat(this byte[] value)
    {
        StringBuilder sb = new StringBuilder();
        foreach (byte b in value)
        {
            sb.Append(b).Append(" ");
        }
        return sb.ToString();
    }

    public static string MyFormat(this ReadOnlySpan<byte> value)
    {
        StringBuilder sb = new StringBuilder();
        foreach (byte b in value)
        {
            sb.Append(b).Append(" ");
        }
        return sb.ToString();
    }
}

结果:

85 52
85 52

您也可以试试:

public static class MyExtensionMethods
{
    public static string MyFormat(this byte[] value) => Encoding.Unicode.GetString(value);

    public static string MyFormat(this ReadOnlySpan<byte> value) => Encoding.Unicode.GetString(value);
}