Skip to content

[API Proposal]: Add format-aware parsable interfaces #129772

Description

@OleksandrTsvirkun

Background and motivation

IParsable<TSelf>, ISpanParsable<TSelf>, and IUtf8SpanParsable<TSelf> provide generic parsing contracts for string, UTF-16 span, and UTF-8 span input.

However, these interfaces only model general parsing:

TSelf.Parse(value, provider);
TSelf.TryParse(value, provider, out result);

Several .NET types also expose exact parsing APIs where the caller provides the expected format explicitly:

TimeSpan.ParseExact(value, format, provider);
TimeSpan.TryParseExact(value, format, provider, out result);

This capability is currently not expressible in generic code. A generic algorithm can constrain TSelf to IParsable<TSelf>, ISpanParsable<TSelf>, or IUtf8SpanParsable<TSelf>, but it cannot express that TSelf supports exact parsing using a caller-supplied format.

Exact parsing is also naturally paired with formatting. If a type can be parsed using a specific format, generic code often also needs to format the same type using a compatible format. Therefore, the proposed interfaces compose parsing contracts with the corresponding formatting contracts:

  • IFormatParsable<TSelf> composes IParsable<TSelf> with IFormattable;
  • IFormatSpanParsable<TSelf> composes ISpanParsable<TSelf> with ISpanFormattable;
  • IUtf8FormatSpanParsable<TSelf> composes IUtf8SpanParsable<TSelf> with IUtf8SpanFormattable.

This proposal is a revised and expanded version of the idea previously discussed in #121379.

API Proposal

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;

namespace System
{
    /// <summary>Defines a mechanism for formatting a value and parsing a string to a value using a specified format.</summary>
    /// <typeparam name="TSelf">The type that implements this interface.</typeparam>
    public interface IFormatParsable<TSelf> : IParsable<TSelf>, IFormattable
        where TSelf : IFormatParsable<TSelf>?
    {
        /// <summary>Parses a string into a value using a specified format.</summary>
        /// <param name="s">The string to parse.</param>
        /// <param name="format">The required format of <paramref name="s" />.</param>
        /// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param>
        /// <returns>The result of parsing <paramref name="s" /> using <paramref name="format" />.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="s" /> is <c>null</c>.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="format" /> is <c>null</c>.</exception>
        /// <exception cref="FormatException"><paramref name="s" /> is not in the format specified by <paramref name="format" />.</exception>
        /// <exception cref="OverflowException"><paramref name="s" /> is not representable by <typeparamref name="TSelf" />.</exception>
        static abstract TSelf ParseExact(string s, string format, IFormatProvider? provider);

        /// <summary>Tries to parse a string into a value using a specified format.</summary>
        /// <param name="s">The string to parse.</param>
        /// <param name="format">The required format of <paramref name="s" />.</param>
        /// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param>
        /// <param name="result">On return, contains the result of successfully parsing <paramref name="s" /> using <paramref name="format" /> or an undefined value on failure.</param>
        /// <returns><c>true</c> if <paramref name="s" /> was successfully parsed using <paramref name="format" />; otherwise, <c>false</c>.</returns>
        static abstract bool TryParseExact(
            [NotNullWhen(true)] string? s,
            string? format,
            IFormatProvider? provider,
            [MaybeNullWhen(returnValue: false)] out TSelf result);
    }
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;

namespace System
{
    /// <summary>Defines a mechanism for formatting a value and parsing a span of characters to a value using a specified format.</summary>
    /// <typeparam name="TSelf">The type that implements this interface.</typeparam>
    public interface IFormatSpanParsable<TSelf> : ISpanParsable<TSelf>, IFormatParsable<TSelf>, ISpanFormattable
        where TSelf : IFormatSpanParsable<TSelf>?
    {
        /// <summary>Parses a span of characters into a value using a specified format.</summary>
        /// <param name="s">The span of characters to parse.</param>
        /// <param name="format">The required format of <paramref name="s" />.</param>
        /// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param>
        /// <returns>The result of parsing <paramref name="s" /> using <paramref name="format" />.</returns>
        /// <exception cref="FormatException"><paramref name="s" /> is not in the format specified by <paramref name="format" />.</exception>
        /// <exception cref="OverflowException"><paramref name="s" /> is not representable by <typeparamref name="TSelf" />.</exception>
        static abstract TSelf ParseExact(
            ReadOnlySpan<char> s,
            ReadOnlySpan<char> format,
            IFormatProvider? provider);

        /// <summary>Tries to parse a span of characters into a value using a specified format.</summary>
        /// <param name="s">The span of characters to parse.</param>
        /// <param name="format">The required format of <paramref name="s" />.</param>
        /// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param>
        /// <param name="result">On return, contains the result of successfully parsing <paramref name="s" /> using <paramref name="format" /> or an undefined value on failure.</param>
        /// <returns><c>true</c> if <paramref name="s" /> was successfully parsed using <paramref name="format" />; otherwise, <c>false</c>.</returns>
        static abstract bool TryParseExact(
            ReadOnlySpan<char> s,
            ReadOnlySpan<char> format,
            IFormatProvider? provider,
            [MaybeNullWhen(returnValue: false)] out TSelf result);
    }
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;

namespace System
{
    /// <summary>Defines a mechanism for formatting a value as UTF-8 and parsing a span of UTF-8 characters to a value using a specified format.</summary>
    /// <typeparam name="TSelf">The type that implements this interface.</typeparam>
    public interface IUtf8FormatSpanParsable<TSelf> : IUtf8SpanParsable<TSelf>, IUtf8SpanFormattable
        where TSelf : IUtf8FormatSpanParsable<TSelf>?
    {
        /// <summary>Parses a span of UTF-8 characters into a value using a specified format.</summary>
        /// <param name="utf8Text">The span of UTF-8 characters to parse.</param>
        /// <param name="format">The required format of <paramref name="utf8Text" />.</param>
        /// <param name="provider">An object that provides culture-specific formatting information about <paramref name="utf8Text" />.</param>
        /// <returns>The result of parsing <paramref name="utf8Text" /> using <paramref name="format" />.</returns>
        /// <exception cref="FormatException"><paramref name="utf8Text" /> is not in the format specified by <paramref name="format" />.</exception>
        /// <exception cref="OverflowException"><paramref name="utf8Text" /> is not representable by <typeparamref name="TSelf" />.</exception>
        static abstract TSelf ParseExact(
            ReadOnlySpan<byte> utf8Text,
            ReadOnlySpan<char> format,
            IFormatProvider? provider);

        /// <summary>Tries to parse a span of UTF-8 characters into a value using a specified format.</summary>
        /// <param name="utf8Text">The span of UTF-8 characters to parse.</param>
        /// <param name="format">The required format of <paramref name="utf8Text" />.</param>
        /// <param name="provider">An object that provides culture-specific formatting information about <paramref name="utf8Text" />.</param>
        /// <param name="result">On return, contains the result of successfully parsing <paramref name="utf8Text" /> using <paramref name="format" /> or an undefined value on failure.</param>
        /// <returns><c>true</c> if <paramref name="utf8Text" /> was successfully parsed using <paramref name="format" />; otherwise, <c>false</c>.</returns>
        static abstract bool TryParseExact(
            ReadOnlySpan<byte> utf8Text,
            ReadOnlySpan<char> format,
            IFormatProvider? provider,
            [MaybeNullWhen(returnValue: false)] out TSelf result);
    }
}

API Usage

String-based exact parsing and formatting

using System;

public static class FormatConversion
{
    public static TSelf ParseExact<TSelf>(
        string value,
        string format,
        IFormatProvider? provider = null)
        where TSelf : IFormatParsable<TSelf>
    {
        return TSelf.ParseExact(value, format, provider);
    }

    public static bool TryParseExact<TSelf>(
        string? value,
        string? format,
        IFormatProvider? provider,
        out TSelf result)
        where TSelf : IFormatParsable<TSelf>
    {
        return TSelf.TryParseExact(value, format, provider, out result);
    }

    public static string Format<TSelf>(
        TSelf value,
        string? format,
        IFormatProvider? provider = null)
        where TSelf : IFormatParsable<TSelf>
    {
        return value.ToString(format, provider);
    }
}

UTF-16 span-based exact parsing and formatting

using System;

public static class SpanFormatConversion
{
    public static TSelf ParseExact<TSelf>(
        ReadOnlySpan<char> value,
        ReadOnlySpan<char> format,
        IFormatProvider? provider = null)
        where TSelf : IFormatSpanParsable<TSelf>
    {
        return TSelf.ParseExact(value, format, provider);
    }

    public static bool TryParseExact<TSelf>(
        ReadOnlySpan<char> value,
        ReadOnlySpan<char> format,
        IFormatProvider? provider,
        out TSelf result)
        where TSelf : IFormatSpanParsable<TSelf>
    {
        return TSelf.TryParseExact(value, format, provider, out result);
    }

    public static bool TryFormat<TSelf>(
        TSelf value,
        Span<char> destination,
        out int charsWritten,
        ReadOnlySpan<char> format,
        IFormatProvider? provider = null)
        where TSelf : IFormatSpanParsable<TSelf>
    {
        return value.TryFormat(destination, out charsWritten, format, provider);
    }
}

UTF-8 exact parsing and formatting

using System;

public static class Utf8FormatConversion
{
    public static TSelf ParseExact<TSelf>(
        ReadOnlySpan<byte> utf8Text,
        ReadOnlySpan<char> format,
        IFormatProvider? provider = null)
        where TSelf : IUtf8FormatSpanParsable<TSelf>
    {
        return TSelf.ParseExact(utf8Text, format, provider);
    }

    public static bool TryParseExact<TSelf>(
        ReadOnlySpan<byte> utf8Text,
        ReadOnlySpan<char> format,
        IFormatProvider? provider,
        out TSelf result)
        where TSelf : IUtf8FormatSpanParsable<TSelf>
    {
        return TSelf.TryParseExact(utf8Text, format, provider, out result);
    }

    public static bool TryFormat<TSelf>(
        TSelf value,
        Span<byte> utf8Destination,
        out int bytesWritten,
        ReadOnlySpan<char> format,
        IFormatProvider? provider = null)
        where TSelf : IUtf8FormatSpanParsable<TSelf>
    {
        return value.TryFormat(utf8Destination, out bytesWritten, format, provider);
    }
}

Example implementation

The following example shows a domain-specific Money value object that supports:

  • general parsing;
  • exact parsing;
  • formatting;
  • span-based exact parsing;
  • span-based formatting.

The example uses two custom formats:

"A C" // amount followed by currency, for example: 123.45 USD
"C A" // currency followed by amount, for example: USD 123.45
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;

public readonly record struct Money(decimal Amount, string Currency)
    : IFormatSpanParsable<Money>
{
    public static Money Parse(string s, IFormatProvider? provider)
    {
        ArgumentNullException.ThrowIfNull(s);

        if (!TryParse(s, provider, out Money result))
        {
            throw new FormatException();
        }

        return result;
    }

    public static bool TryParse(
        [NotNullWhen(true)] string? s,
        IFormatProvider? provider,
        out Money result)
    {
        if (s is null)
        {
            result = default;
            return false;
        }

        return TryParse(s.AsSpan(), provider, out result);
    }

    public static Money Parse(ReadOnlySpan<char> s, IFormatProvider? provider)
    {
        if (!TryParse(s, provider, out Money result))
        {
            throw new FormatException();
        }

        return result;
    }

    public static bool TryParse(
        ReadOnlySpan<char> s,
        IFormatProvider? provider,
        out Money result)
    {
        return TryParseExact(s, "A C", provider, out result)
            || TryParseExact(s, "C A", provider, out result);
    }

    public static Money ParseExact(
        string s,
        string format,
        IFormatProvider? provider)
    {
        ArgumentNullException.ThrowIfNull(s);
        ArgumentNullException.ThrowIfNull(format);

        return ParseExact(s.AsSpan(), format.AsSpan(), provider);
    }

    public static bool TryParseExact(
        [NotNullWhen(true)] string? s,
        string? format,
        IFormatProvider? provider,
        out Money result)
    {
        if (s is null || format is null)
        {
            result = default;
            return false;
        }

        return TryParseExact(s.AsSpan(), format.AsSpan(), provider, out result);
    }

    public static Money ParseExact(
        ReadOnlySpan<char> s,
        ReadOnlySpan<char> format,
        IFormatProvider? provider)
    {
        if (!TryParseExact(s, format, provider, out Money result))
        {
            throw new FormatException();
        }

        return result;
    }

    public static bool TryParseExact(
        ReadOnlySpan<char> s,
        ReadOnlySpan<char> format,
        IFormatProvider? provider,
        out Money result)
    {
        if (format.SequenceEqual("A C"))
        {
            int separatorIndex = s.LastIndexOf(' ');

            if (separatorIndex <= 0 || separatorIndex == s.Length - 1)
            {
                result = default;
                return false;
            }

            ReadOnlySpan<char> amountText = s[..separatorIndex];
            ReadOnlySpan<char> currencyText = s[(separatorIndex + 1)..];

            return TryCreate(amountText, currencyText, provider, out result);
        }

        if (format.SequenceEqual("C A"))
        {
            int separatorIndex = s.IndexOf(' ');

            if (separatorIndex <= 0 || separatorIndex == s.Length - 1)
            {
                result = default;
                return false;
            }

            ReadOnlySpan<char> currencyText = s[..separatorIndex];
            ReadOnlySpan<char> amountText = s[(separatorIndex + 1)..];

            return TryCreate(amountText, currencyText, provider, out result);
        }

        result = default;
        return false;
    }

    public override string ToString()
    {
        return ToString("A C", CultureInfo.InvariantCulture);
    }

    public string ToString(string? format, IFormatProvider? formatProvider)
    {
        format ??= "A C";

        return format switch
        {
            "A C" => string.Create(
                formatProvider,
                $"{Amount:0.00} {Currency}"),

            "C A" => string.Create(
                formatProvider,
                $"{Currency} {Amount:0.00}"),

            _ => throw new FormatException()
        };
    }

    public bool TryFormat(
        Span<char> destination,
        out int charsWritten,
        ReadOnlySpan<char> format,
        IFormatProvider? provider)
    {
        if (format.IsEmpty || format.SequenceEqual("A C"))
        {
            return destination.TryWrite(
                provider,
                $"{Amount:0.00} {Currency}",
                out charsWritten);
        }

        if (format.SequenceEqual("C A"))
        {
            return destination.TryWrite(
                provider,
                $"{Currency} {Amount:0.00}",
                out charsWritten);
        }

        charsWritten = 0;
        return false;
    }

    private static bool TryCreate(
        ReadOnlySpan<char> amountText,
        ReadOnlySpan<char> currencyText,
        IFormatProvider? provider,
        out Money result)
    {
        if (currencyText.Length == 3 &&
            decimal.TryParse(amountText, NumberStyles.Number, provider, out decimal amount))
        {
            result = new Money(amount, currencyText.ToString().ToUpperInvariant());
            return true;
        }

        result = default;
        return false;
    }
}

Design notes

Why inherit formatting interfaces?

The proposed interfaces are format-aware parsing contracts. In practice, format-aware parsing is usually paired with format-aware formatting.

For example, a serializer, UI binding layer, command-line binder, validation component, or domain-specific conversion pipeline may need to round-trip a value using the same format:

string text = value.ToString(format, provider);
TSelf parsed = TSelf.ParseExact(text, format, provider);

Requiring the corresponding formatting interface makes the contract more useful for generic conversion pipelines and provides a symmetrical format-aware conversion model.

Why no styles parameter?

This proposal intentionally does not include a styles parameter.

Existing BCL APIs use type-specific style enums such as:

DateTimeStyles
TimeSpanStyles
NumberStyles

There is no single style enum that correctly applies to all format-aware parsable types. A style-aware generic interface could be considered separately, but it should not be required for the minimal exact parsing contract.

Why is the UTF-8 input format still ReadOnlySpan<char>?

IUtf8FormatSpanParsable<TSelf> uses ReadOnlySpan<byte> for the input because the value being parsed is UTF-8 encoded text.

The format parameter remains ReadOnlySpan<char> because .NET formatting APIs generally model format strings as string or ReadOnlySpan<char>, even when the formatted output is UTF-8. The format describes the grammar; it is not the parsed input payload.

Why not require IUtf8FormatSpanParsable<TSelf> to inherit IFormatSpanParsable<TSelf>?

The UTF-8 parsing contract can be useful independently from the UTF-16 span parsing contract. A type may want to support direct UTF-8 parsing and formatting without requiring generic callers to rely on UTF-16 exact parsing.

However, BCL types that support all forms can implement both interfaces:

public readonly struct SomeType :
    IFormatSpanParsable<SomeType>,
    IUtf8FormatSpanParsable<SomeType>
{
}

Why use ParseExact / TryParseExact names?

These names match existing BCL exact parsing APIs and clearly distinguish format-constrained parsing from general parsing.

The proposed interfaces do not invent a new parsing concept. They expose an existing BCL pattern as a generic static abstract contract.

Alternative Designs

Keep the existing concrete APIs only

One option is to avoid adding new interfaces and keep exact parsing available only as concrete type-specific APIs.

For example, existing types can continue exposing APIs such as:

SomeType.ParseExact(value, format, provider);
SomeType.TryParseExact(value, format, provider, out result);

This avoids increasing the public interface surface area. It also avoids imposing a common shape on APIs whose exact parsing semantics may differ by type.

The downside is that this preserves the current generic programming gap. Generic code can express general parsing through IParsable<TSelf>, ISpanParsable<TSelf>, and IUtf8SpanParsable<TSelf>, but it cannot express that a type supports format-constrained parsing.

Add parsing-only interfaces

Another option is to introduce exact parsing interfaces that do not inherit formatting interfaces:

public interface IFormatParsable<TSelf> : IParsable<TSelf>
    where TSelf : IFormatParsable<TSelf>?
{
    static abstract TSelf ParseExact(
        string s,
        string format,
        IFormatProvider? provider);

    static abstract bool TryParseExact(
        string? s,
        string? format,
        IFormatProvider? provider,
        out TSelf result);
}

The span and UTF-8 variants could follow the same parsing-only pattern.

This design is smaller and avoids requiring formatting support from types that only want to expose exact parsing. It may also better preserve the separation between parsing and formatting contracts.

The downside is that many format-aware scenarios are round-trip scenarios. Serializers, binders, validation layers, UI components, and import/export pipelines often need both operations:

string text = value.ToString(format, provider);
TSelf parsed = TSelf.ParseExact(text, format, provider);

Without formatting inheritance, these scenarios would still require separate constraints such as IFormattable, ISpanFormattable, or IUtf8SpanFormattable.

Add only the string-based interface

Another option is to add only IFormatParsable<TSelf> and omit span-based interfaces.

This would provide the minimal generic contract for exact parsing and would avoid introducing multiple related interfaces at once.

The downside is that it would be inconsistent with the existing parsing interface family. .NET already has separate contracts for string, UTF-16 span, and UTF-8 span parsing. Adding only the string-based exact parsing contract would leave high-performance span-based generic code without a corresponding API.

Add only string and UTF-16 span interfaces

Another option is to add only:

IFormatParsable<TSelf>
IFormatSpanParsable<TSelf>

and omit the UTF-8 counterpart.

This would be a smaller proposal and would still cover the most common string and UTF-16 span scenarios.

The downside is that IUtf8SpanParsable<TSelf> would remain without an exact parsing counterpart. Since .NET already has UTF-8 parsing and UTF-8 formatting interfaces, omitting a UTF-8 exact parsing interface would make the interface family asymmetric and less useful for libraries that operate directly on UTF-8 data.

Require UTF-8 exact parsing to also support UTF-16 exact parsing

Another possible shape is:

public interface IUtf8FormatSpanParsable<TSelf> :
    IFormatSpanParsable<TSelf>,
    IUtf8SpanParsable<TSelf>,
    IUtf8SpanFormattable
    where TSelf : IUtf8FormatSpanParsable<TSelf>?
{
}

This would guarantee that any UTF-8 exact parsable type also supports UTF-16 exact parsing.

The downside is that UTF-8 parsing can be useful independently. Libraries that operate directly on UTF-8 payloads may not want their generic constraints to imply a UTF-16 exact parsing dependency.

Use a generic style parameter

A style-aware design could introduce an additional type parameter:

public interface IFormatParsable<TSelf, TStyles> : IFormatParsable<TSelf>
    where TSelf : IFormatParsable<TSelf, TStyles>?
    where TStyles : struct, Enum
{
    static abstract TSelf ParseExact(
        string s,
        string format,
        IFormatProvider? provider,
        TStyles styles);

    static abstract bool TryParseExact(
        string? s,
        string? format,
        IFormatProvider? provider,
        TStyles styles,
        out TSelf result);
}

This could model APIs that use type-specific style enums.

The downside is that style enum semantics are not common across all parsable types. A style-aware generic interface would be heavier and would likely require a separate proposal.

Use UTF-8 format strings for UTF-8 parsing

Another option is to make the UTF-8 interface fully byte-based:

static abstract TSelf ParseExact(
    ReadOnlySpan<byte> utf8Text,
    ReadOnlySpan<byte> utf8Format,
    IFormatProvider? provider);

This avoids transcoding the format string when the caller already has it as UTF-8.

The downside is that .NET formatting APIs generally model format strings as string or ReadOnlySpan<char>, even when the payload being formatted or parsed is UTF-8. Keeping format as ReadOnlySpan<char> aligns better with the existing formatting model and avoids introducing a second format-string representation.

Use extension methods, reflection, or source generation instead of interfaces

Another option is to avoid new interfaces and provide helper methods that discover ParseExact and TryParseExact by convention, reflection, or source-generated adapters.

This avoids new public interfaces, but it loses compile-time generic constraints and static abstract dispatch. It also makes the model less consistent with the existing parsing and formatting interfaces.

Risks

Increased public API surface area

The proposal adds new public interfaces to System. This increases the long-term API surface area and should be justified by concrete generic programming scenarios rather than by symmetry alone.

Once added, the interface names, inheritance shape, method signatures, and nullability annotations become long-term compatibility commitments.

Potential over-constraining through formatting inheritance

The proposed interfaces inherit the corresponding formatting interfaces. This is useful for round-trip scenarios, but it also makes the contracts heavier.

A type that supports exact parsing but does not support formatting would be unable to implement the proposed interface shape. This risk can be reduced by using parsing-only interfaces or by keeping formatting inheritance as an explicit API review decision.

Type-specific format string semantics

The meaning of format is type-specific. The proposal does not define a common format-string language.

Generic code can pass a format string to TSelf.ParseExact, but it generally cannot interpret that format string itself. Invalid or unsupported formats remain the responsibility of the implementing type.

Nullability and exception behavior differences

ParseExact and TryParseExact have different failure models. ParseExact throws for invalid input, while TryParseExact returns false.

The exact nullability and failure behavior for format needs careful review. For example, TryParseExact(string? s, string? format, ...) could return false when format is null, while concrete existing APIs may use non-null string format or may throw for null format values.

Inconsistency with existing richer concrete APIs

Some concrete types expose richer exact parsing overloads, such as overloads that accept multiple formats or type-specific style parameters.

The proposed interfaces intentionally model only the minimal common exact parsing shape. This means the interfaces would not expose every existing exact parsing capability.

Implementation burden

If existing framework types implement these interfaces, each implementation requires API updates, tests, documentation, and compatibility review.

Not every type that supports exact parsing necessarily supports all proposed input forms. Some types may support string and UTF-16 span APIs but not UTF-8 APIs, or vice versa.

Compatibility expectations for static abstract interface members

Static abstract interface members are compile-time contracts. Adding these interfaces is not a breaking change by itself, but implementing them on existing types creates new public surface area and long-term behavioral expectations.

Care is needed to avoid introducing inconsistent behavior across types.

Possible performance regressions in naive implementations

The interfaces themselves do not require allocations, especially in the span-based APIs. However, naive implementations could allocate when converting between string, ReadOnlySpan<char>, and ReadOnlySpan<byte>.

Implementations should avoid unnecessary transcoding or string allocation, especially for span and UTF-8 APIs.

Generic constraints may exclude otherwise valid types

If generic libraries adopt these interfaces aggressively, they may exclude types that support equivalent exact parsing functionality through concrete APIs but do not implement the new interfaces.

This is a general risk of introducing new marker-style capability interfaces and should be considered during API review.

Open questions

  1. Should TryParseExact accept string? format and return false for null, or should format be non-null?

  2. Should IUtf8FormatSpanParsable<TSelf> inherit IFormatSpanParsable<TSelf>, or should UTF-8 exact parsing remain independent?

  3. Should the first implementation be limited to date/time-related types that already expose exact parsing APIs?

  4. Should concrete BCL implementations use StringSyntaxAttribute on their concrete methods where the format syntax is known, while the generic interfaces remain unannotated because the valid format syntax depends on TSelf?

  5. Should IFormatParsable<TSelf> require IFormattable, or should exact parsing and formatting remain separate contracts?

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.RuntimeuntriagedNew issue has not been triaged by the area owner

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions