Home > Back-end >  Formatting byte[] or ReadOnlySpan<byte> to string using C# 10 and .NET 6 string interpolation
Formatting byte[] or ReadOnlySpan<byte> to string using C# 10 and .NET 6 string interpolation

Time:04-04

I would like to format byte[] and ReadOnlySpan<byte> bytes to strings using a few formatting parameters. Say, like S for Base64. For the purpose of this, the length is always fixed to some known constant.

I would like to use the modern C# 10 and .NET 6 string formatting capabilities as described at https://devblogs.microsoft.com/dotnet/string-interpolation-in-c-10-and-net-6/.

I took some code from that post and modified it a bit in the code embedded as follows. It's also at https://dotnetfiddle.net/svyQKD.

As is seen in the code, I get the direct method call to succeed for byte[], but not for ReadOnlySpan<byte>.

Does anyone have a clue how to do that?

I suspect I need InterpolatedStringHandler. But if that is the case, then it looks like I don't know how to implement one. All tips and code tricks would probably help. I've been stuck at this for a while now and it's getting into wee hours. :)

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:2}");

        // How to make this work? This does not compile. (Yes, signature problem. How to define it?).

        // Console.WriteLine(StringExtensions.FormatString(test2));
    }
}

CodePudding user response:

You can use an extension method:

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();
    }
}

Result:

85 52
85 52

CodePudding user response:

If you really want to use the method like this, you need to override the ToString() method of the class "Byte[]".

But you cannot override the method on the very class Byte[]. You need to inherit the class Byte[] and override the ToString() method on the derivated.

Then, you must replace all your Byte[] objects with the derivated class, with is not a good idea.

So, there is no solution for you in this manner:

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

The best you can do is create an "outside" method for formating the Byte[] and do as you with for formating.

*The same applies to the ReadOnlySpan

  • Related