예제 #1
0
        /// <summary>
        /// Formats a Guid as a UTF8 string.
        /// </summary>
        /// <param name="value">Value to format</param>
        /// <param name="destination">Buffer to write the UTF8-formatted value to</param>
        /// <param name="bytesWritten">Receives the length of the formatted text in bytes</param>
        /// <param name="format">The standard format to use</param>
        /// <returns>
        /// true for success. "bytesWritten" contains the length of the formatted text in bytes.
        /// false if buffer was too short. Iteratively increase the size of the buffer and retry until it succeeds.
        /// </returns>
        /// <remarks>
        /// Formats supported:
        ///     D (default)     nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn
        ///     B               {nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn}
        ///     P               (nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn)
        ///     N               nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
        /// </remarks>
        /// <exceptions>
        /// <cref>System.FormatException</cref> if the format is not valid for this data type.
        /// </exceptions>
        public static bool TryFormat(Guid value, Span <byte> destination, out int bytesWritten, StandardFormat format = default)
        {
            const int INSERT_DASHES       = unchecked ((int)0x80000000);
            const int NO_DASHES           = 0;
            const int INSERT_CURLY_BRACES = (CloseBrace << 16) | (OpenBrace << 8);
            const int INSERT_ROUND_BRACES = (CloseParen << 16) | (OpenParen << 8);
            const int NO_BRACES           = 0;
            const int LEN_GUID_BASE       = 32;
            const int LEN_ADD_DASHES      = 4;
            const int LEN_ADD_BRACES      = 2;

            // This is a 32-bit value whose contents (where 0 is the low byte) are:
            // 0th byte: minimum required length of the output buffer,
            // 1st byte: the ASCII byte to insert for the opening brace position (or 0 if no braces),
            // 2nd byte: the ASCII byte to insert for the closing brace position (or 0 if no braces),
            // 3rd byte: high bit set if dashes are to be inserted.
            //
            // The reason for keeping a single flag instead of separate vars is that we can avoid register spillage
            // as we build up the output value.
            int flags;

            switch (FormattingHelpers.GetSymbolOrDefault(format, 'D'))
            {
            case 'D':     // nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn
                flags = INSERT_DASHES + NO_BRACES + LEN_GUID_BASE + LEN_ADD_DASHES;
                break;

            case 'B':     // {nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn}
                flags = INSERT_DASHES + INSERT_CURLY_BRACES + LEN_GUID_BASE + LEN_ADD_DASHES + LEN_ADD_BRACES;
                break;

            case 'P':     // (nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn)
                flags = INSERT_DASHES + INSERT_ROUND_BRACES + LEN_GUID_BASE + LEN_ADD_DASHES + LEN_ADD_BRACES;
                break;

            case 'N':     // nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
                flags = NO_BRACES + NO_DASHES + LEN_GUID_BASE;
                break;

            default:
                return(ThrowHelper.TryFormatThrowFormatException(out bytesWritten));
            }

            // At this point, the low byte of flags contains the minimum required length

            if ((byte)flags > destination.Length)
            {
                bytesWritten = 0;
                return(false);
            }

            bytesWritten = (byte)flags;
            flags      >>= 8;

            // At this point, the low byte of flags contains the opening brace char (if any)

            if ((byte)flags != 0)
            {
                destination[0] = (byte)flags;
                destination    = destination.Slice(1);
            }
            flags >>= 8;

            // At this point, the low byte of flags contains the closing brace char (if any)
            // And since we're performing arithmetic shifting the high bit of flags is set (flags is negative) if dashes are required

            DecomposedGuid guidAsBytes = default;

            guidAsBytes.Guid = value;

            // When a GUID is blitted, the first three components are little-endian, and the last component is big-endian.

            // The line below forces the JIT to hoist the bounds check for the following segment.
            // The JIT will optimize away the read, but it cannot optimize away the bounds check
            // because it may have an observable side effect (throwing).
            // We use 8 instead of 7 so that we also capture the dash if we're asked to insert one.

            { var unused = destination[8]; }
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte03, destination, 0, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte02, destination, 2, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte01, destination, 4, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte00, destination, 6, FormattingHelpers.HexCasing.Lowercase);

            if (flags < 0 /* use dash? */)
            {
                destination[8] = Dash;
                destination    = destination.Slice(9);
            }
            else
            {
                destination = destination.Slice(8);
            }

            { var unused = destination[4]; }
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte05, destination, 0, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte04, destination, 2, FormattingHelpers.HexCasing.Lowercase);

            if (flags < 0 /* use dash? */)
            {
                destination[4] = Dash;
                destination    = destination.Slice(5);
            }
            else
            {
                destination = destination.Slice(4);
            }

            { var unused = destination[4]; }
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte07, destination, 0, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte06, destination, 2, FormattingHelpers.HexCasing.Lowercase);

            if (flags < 0 /* use dash? */)
            {
                destination[4] = Dash;
                destination    = destination.Slice(5);
            }
            else
            {
                destination = destination.Slice(4);
            }

            { var unused = destination[4]; }
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte08, destination, 0, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte09, destination, 2, FormattingHelpers.HexCasing.Lowercase);

            if (flags < 0 /* use dash? */)
            {
                destination[4] = Dash;
                destination    = destination.Slice(5);
            }
            else
            {
                destination = destination.Slice(4);
            }

            { var unused = destination[11]; } // can't hoist bounds check on the final brace (if exists)
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte10, destination, 0, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte11, destination, 2, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte12, destination, 4, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte13, destination, 6, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte14, destination, 8, FormattingHelpers.HexCasing.Lowercase);
            FormattingHelpers.WriteHexByte(guidAsBytes.Byte15, destination, 10, FormattingHelpers.HexCasing.Lowercase);

            if ((byte)flags != 0)
            {
                destination[12] = (byte)flags;
            }

            return(true);
        }