Skip to content

[API Proposal]: Implement IUtf8SpanParsable on IPAddress & IPNetwork #103111

Closed
@edwardneal

Description

@edwardneal

Background and motivation

This continues the work in #81500, expanding the existing UTF8 support to IPAddress and IPNetwork. I'm trying to tackle the low-hanging from from the list of expected types.

cc @tannergooding and linking this to the earlier PR #102144 - I can see the issue's gone to System.Net rather than System.Runtime.

API Proposal

The original issue expected IUtf8SpanParsable to be implemented implicitly, and I've done this on IPAddress. I've implemented it explicitly on IPNetwork though, adding an additional pair of Parse and TryParse overloads. This is because IPNetwork also implements ISpanParsable<IPAddress> explicitly, and I wanted to keep the API surface consistent between the two interface implementations. The original justification for it to implement ISpanParsable<IPAddress> explicitly was that it doesn't support custom formatting (API review results here), and I believe the same rationale applies here.

namespace System.Net;

public partial class IPAddress
    : ISpanFormattable, ISpanParsable<IPAddress>, IUtf8SpanFormattable
+   , IUtf8SpanParsable<IPAddress>
{
+   public static IPAddress Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider);

+   public static bool TryParse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider,
+       [NotNullWhen(true)] out IPAddress? result);
}

public readonly partial struct IPNetwork
    : ISpanFormattable, ISpanParsable<IPAddress>, IUtf8SpanFormattable
+   , IUtf8SpanParsable<IPNetwork>
{
    static IPNetwork ISpanParsable<IPNetwork>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider);
    public static IPNetwork Parse(ReadOnlySpan<char> s);
+   static IPNetwork IUtf8SpanParsable<IPNetwork>.Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider);
+   public static IPNetwork Parse(ReadOnlySpan<byte> utf8Text);

    static bool ISpanParsable<IPNetwork>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out IPNetwork result);
    public static bool TryParse(ReadOnlySpan<char> s, out IPNetwork result);
+   static bool IUtf8SpanParsable<IPNetwork>.TryParse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider,
+       out IPNetwork result);
+   public static bool TryParse(ReadOnlySpan<byte> utf8Text,
+       [MaybeNullWhen(false)] out IPNetwork? result);
}

API Usage

ReadOnlySpan<byte> utf8Localhost = "127.0.0.1"u8;
ReadOnlySpan<byte> utf8LocalhostNetwork = "127.0.0.0/8"u8;

IPAddress parsedLocalhost = IPAddress.Parse(utf8Localhost, null);
IPNetwork parsedLocalhostNetwork = IPNetwork.Parse(utf8LocalhostNetwork);

bool successfullyParsed;
successfullyParsed = IPAddress.TryParse(utf8Localhost, null, out parsedLocalhost);
successfullyParsed = IPNetwork.TryParse(utf8LocalhostNetwork, null, out parsedLocalhostNetwork);

Alternative Designs

No response

Risks

At present, all parsing of IPAddresses from ReadOnlySpan<char> is handled by IPAddressParser, IPv4AddressHelper and IPv6AddressHelper. This works completely with chars and performs minimal allocations. I'd prefer to make these classes generic, but their codebases are also used privately in Uri parsing and by System.Net.Quic. The work required would also replace some of the of references to char* with ReadOnlySpan<char> in Uri (and might slightly improve GC performance by eliminating a few cases where we pin strings), but the changes cut across more projects.

I could alternatively implement the interface by just calling Encoding.UTF8.GetChars on the UTF8 input and passing through to ISpanParsable<IPAddress>.Parse, leaving the underlying IP address parsing as char-only. The longest possible IP address would fit in a 64-character stackalloc'd buffer, so I don't think this approach would necessarily need to allocate - it's just a tradeoff between the wider impact of changing IPAddressParser and any efficiency loss from a separate transcoding stage.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-approvedAPI was approved in API review, it can be implementedarea-System.Netin-prThere is an active PR which will close this issue when it is merged

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions