Skip to content

Commit 09e6ddd

Browse files
[API Proposal]: Implement IParsable<TSelf> et cetera on IPEndPoint (#118095)
* [API Proposal]: Implement IParsable<TSelf> et cetera on IPEndPoint * fix remarks * fix xunit analyzer error * fix remarks 3 * add comments * Update src/libraries/System.Net.Primitives/src/System/Net/IPEndPoint.cs --------- Co-authored-by: Marie Píchová <[email protected]>
1 parent 7f6aedb commit 09e6ddd

File tree

3 files changed

+422
-12
lines changed

3 files changed

+422
-12
lines changed

src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ public IPAddress(System.ReadOnlySpan<byte> address, long scopeid) { }
283283
static bool System.IUtf8SpanParsable<System.Net.IPAddress>.TryParse(System.ReadOnlySpan<byte> utf8Text, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.Net.IPAddress? result) { throw null; }
284284
public bool TryWriteBytes(System.Span<byte> destination, out int bytesWritten) { throw null; }
285285
}
286-
public partial class IPEndPoint : System.Net.EndPoint
286+
public partial class IPEndPoint : System.Net.EndPoint, ISpanFormattable, ISpanParsable<IPEndPoint>, IUtf8SpanFormattable, IUtf8SpanParsable<IPEndPoint>
287287
{
288288
public const int MaxPort = 65535;
289289
public const int MinPort = 0;
@@ -301,6 +301,19 @@ public IPEndPoint(System.Net.IPAddress address, int port) { }
301301
public override string ToString() { throw null; }
302302
public static bool TryParse(System.ReadOnlySpan<char> s, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.IPEndPoint? result) { throw null; }
303303
public static bool TryParse(string s, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.IPEndPoint? result) { throw null; }
304+
public static IPEndPoint Parse(ReadOnlySpan<byte> utf8Text) { throw null; }
305+
static IPEndPoint ISpanParsable<IPEndPoint>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider) { throw null; }
306+
static IPEndPoint IParsable<IPEndPoint>.Parse(string s, IFormatProvider? provider) { throw null; }
307+
static IPEndPoint IUtf8SpanParsable<IPEndPoint>.Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider) { throw null; }
308+
public static bool TryParse(ReadOnlySpan<byte> utf8Text, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out IPEndPoint? result) { throw null; }
309+
static bool ISpanParsable<IPEndPoint>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out IPEndPoint? result) { throw null; }
310+
static bool IParsable<IPEndPoint>.TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out IPEndPoint? result) { throw null; }
311+
static bool IUtf8SpanParsable<IPEndPoint>.TryParse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out IPEndPoint? result) { throw null; }
312+
string IFormattable.ToString(string? format, IFormatProvider? formatProvider) { throw null; }
313+
public bool TryFormat(Span<char> destination, out int charsWritten) { throw null; }
314+
public bool TryFormat(Span<byte> utf8Destination, out int bytesWritten) { throw null; }
315+
bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) { throw null; }
316+
bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider) { throw null; }
304317
}
305318
public readonly partial struct IPNetwork : System.IEquatable<System.Net.IPNetwork>, System.IFormattable, System.IParsable<System.Net.IPNetwork>, System.ISpanFormattable, System.ISpanParsable<System.Net.IPNetwork>, System.IUtf8SpanFormattable, System.IUtf8SpanParsable<System.Net.IPNetwork>
306319
{

src/libraries/System.Net.Primitives/src/System/Net/IPEndPoint.cs

Lines changed: 156 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics;
45
using System.Diagnostics.CodeAnalysis;
56
using System.Globalization;
67
using System.Net.Sockets;
8+
using System.Numerics;
9+
using System.Runtime.InteropServices;
10+
using System.Text.Unicode;
711

812
namespace System.Net
913
{
1014
/// <summary>
1115
/// Provides an IP address.
1216
/// </summary>
13-
public class IPEndPoint : EndPoint
17+
public class IPEndPoint : EndPoint, ISpanFormattable, ISpanParsable<IPEndPoint>, IUtf8SpanFormattable, IUtf8SpanParsable<IPEndPoint>
1418
{
1519
/// <summary>
1620
/// Specifies the minimum acceptable value for the <see cref='System.Net.IPEndPoint.Port'/> property.
@@ -92,41 +96,69 @@ public static bool TryParse(string s, [NotNullWhen(true)] out IPEndPoint? result
9296
return TryParse(s.AsSpan(), out result);
9397
}
9498

95-
public static bool TryParse(ReadOnlySpan<char> s, [NotNullWhen(true)] out IPEndPoint? result)
99+
internal static bool InternalTryParse<TChar>(ReadOnlySpan<TChar> s, [NotNullWhen(true)] out IPEndPoint? result)
100+
where TChar : unmanaged, IBinaryInteger<TChar>
96101
{
102+
Debug.Assert(typeof(TChar) == typeof(byte) || typeof(TChar) == typeof(char));
103+
97104
int addressLength = s.Length; // If there's no port then send the entire string to the address parser
98-
int lastColonPos = s.LastIndexOf(':');
105+
int lastColonPos = s.LastIndexOf(TChar.CreateTruncating(':'));
99106

100107
// Look to see if this is an IPv6 address with a port.
101108
if (lastColonPos > 0)
102109
{
103-
if (s[lastColonPos - 1] == ']')
110+
if (s[lastColonPos - 1] == TChar.CreateTruncating(']'))
104111
{
105112
addressLength = lastColonPos;
106113
}
107114
// Look to see if this is IPv4 with a port (IPv6 will have another colon)
108-
else if (s.Slice(0, lastColonPos).LastIndexOf(':') == -1)
115+
else if (s.Slice(0, lastColonPos).LastIndexOf(TChar.CreateTruncating(':')) == -1)
109116
{
110117
addressLength = lastColonPos;
111118
}
112119
}
113120

114-
if (IPAddress.TryParse(s.Slice(0, addressLength), out IPAddress? address))
121+
IPAddress? address = IPAddressParser.Parse(s.Slice(0, addressLength), true);
122+
if (address is not null)
115123
{
116-
uint port = 0;
117-
if (addressLength == s.Length ||
118-
(uint.TryParse(s.Slice(addressLength + 1), NumberStyles.None, CultureInfo.InvariantCulture, out port) && port <= MaxPort))
119-
124+
if (addressLength == s.Length)
120125
{
121-
result = new IPEndPoint(address, (int)port);
126+
result = new IPEndPoint(address, 0);
122127
return true;
123128
}
129+
else
130+
{
131+
uint port;
132+
ReadOnlySpan<TChar> portSpan = s.Slice(addressLength + 1);
133+
bool isConvertedToInt;
134+
135+
if (typeof(TChar) == typeof(byte))
136+
{
137+
isConvertedToInt = uint.TryParse(MemoryMarshal.Cast<TChar, byte>(portSpan), NumberStyles.None, CultureInfo.InvariantCulture, out port);
138+
}
139+
else
140+
{
141+
isConvertedToInt = uint.TryParse(MemoryMarshal.Cast<TChar, char>(portSpan), NumberStyles.None, CultureInfo.InvariantCulture, out port);
142+
}
143+
144+
if (isConvertedToInt && port <= MaxPort)
145+
{
146+
result = new IPEndPoint(address, (int)port);
147+
return true;
148+
}
149+
}
124150
}
125151

126152
result = null;
127153
return false;
128154
}
129155

156+
/// <summary>Tries to convert the character span to its <see cref="IPEndPoint"/> equivalent.</summary>
157+
/// <param name="s">A span container the characters representing the <see cref="IPEndPoint"/> to convert.</param>
158+
/// <param name="result">When this method returns, contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="s" /> if the conversion succeeded, or default if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten.</param>
159+
/// <returns><c>true</c> if <paramref name="s" /> was converted successfully; otherwise, false.</returns>
160+
public static bool TryParse(ReadOnlySpan<char> s, [NotNullWhen(true)] out IPEndPoint? result) => InternalTryParse(s, out result);
161+
130162
public static IPEndPoint Parse(string s)
131163
{
132164
ArgumentNullException.ThrowIfNull(s);
@@ -178,5 +210,118 @@ public override int GetHashCode()
178210
{
179211
return _address.GetHashCode() ^ _port;
180212
}
213+
214+
/// <summary>Converts the UTF-8 span to its <see cref="IPEndPoint"/> equivalent.</summary>
215+
/// <param name="utf8Text">A span containing the characters representing the <see cref="IPEndPoint"/> to convert.</param>
216+
/// <returns>contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="utf8Text" /> if the conversion succeeded</returns>
217+
/// <exception cref="FormatException"><paramref name="utf8Text"/> is invalid</exception>
218+
public static IPEndPoint Parse(ReadOnlySpan<byte> utf8Text)
219+
{
220+
if (TryParse(utf8Text, out IPEndPoint? result))
221+
{
222+
return result;
223+
}
224+
225+
throw new FormatException(SR.bad_endpoint_string);
226+
}
227+
228+
/// <summary>Converts the character span to its <see cref="IPEndPoint"/> equivalent.</summary>
229+
/// <param name="s">A span containing the characters representing the <see cref="IPEndPoint"/> to convert.</param>
230+
/// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param>
231+
/// <returns>contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="s" /> if the conversion succeeded</returns>
232+
/// <exception cref="FormatException"><paramref name="s"/> is invalid</exception>
233+
static IPEndPoint ISpanParsable<IPEndPoint>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider) => Parse(s);
234+
235+
/// <summary>Converts the string to its <see cref="IPEndPoint"/> equivalent.</summary>
236+
/// <param name="s">A string containing the characters representing the <see cref="IPEndPoint"/> to convert.</param>
237+
/// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param>
238+
/// <returns>contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="s" /> if the conversion succeeded</returns>
239+
/// <exception cref="FormatException"><paramref name="s"/> is invalid</exception>
240+
static IPEndPoint IParsable<IPEndPoint>.Parse(string s, IFormatProvider? provider) => Parse(s);
241+
242+
/// <summary>Converts the UTF-8 span to its <see cref="IPEndPoint"/> equivalent.</summary>
243+
/// <param name="utf8Text">A Span containing the UTF-8 characters representing the <see cref="IPEndPoint"/> to convert.</param>
244+
/// <param name="provider">An object that provides culture-specific formatting information about <paramref name="utf8Text" />.</param>
245+
/// <returns>contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="utf8Text" /> if the conversion succeeded</returns>
246+
/// <exception cref="FormatException"><paramref name="utf8Text"/> is invalid</exception>
247+
static IPEndPoint IUtf8SpanParsable<IPEndPoint>.Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider) => Parse(utf8Text);
248+
249+
/// <summary>Tries to convert the UTF-8 span to its <see cref="IPEndPoint"/> equivalent.</summary>
250+
/// <param name="utf8Text">A span containing the UTF-8 characters representing the <see cref="IPEndPoint"/> to convert.</param>
251+
/// <param name="result">When this method returns, contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="utf8Text" /> if the conversion succeeded, or default if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten.</param>
252+
/// <returns><c>true</c> if <paramref name="utf8Text" /> was converted successfully; otherwise, false.</returns>
253+
public static bool TryParse(ReadOnlySpan<byte> utf8Text, [NotNullWhen(true)] out IPEndPoint? result) => InternalTryParse(utf8Text, out result);
254+
255+
/// <summary>Tries to convert the character span to its <see cref="IPEndPoint"/> equivalent.</summary>
256+
/// <param name="s">A span container the characters representing the <see cref="IPEndPoint"/> to convert.</param>
257+
/// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param>
258+
/// <param name="result">When this method returns, contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="s" /> if the conversion succeeded, or default if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten.</param>
259+
/// <returns><c>true</c> if <paramref name="s" /> was converted successfully; otherwise, false.</returns>
260+
static bool ISpanParsable<IPEndPoint>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [NotNullWhen(true)] out IPEndPoint? result) => TryParse(s, out result);
261+
262+
/// <summary>Tries to convert the string to its <see cref="IPEndPoint"/> equivalent.</summary>
263+
/// <param name="s">A string representing the <see cref="IPEndPoint"/> to convert.</param>
264+
/// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param>
265+
/// <param name="result">When this method returns, contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="s" /> if the conversion succeeded, or default if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten.</param>
266+
/// <returns><c>true</c> if <paramref name="s" /> was converted successfully; otherwise, false.</returns>
267+
static bool IParsable<IPEndPoint>.TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [NotNullWhen(true)] out IPEndPoint? result)
268+
{
269+
if (s is null)
270+
{
271+
result = default;
272+
return false;
273+
}
274+
275+
return TryParse(s, out result);
276+
}
277+
278+
/// <summary>Tries to convert the UTF-8 span to its <see cref="IPEndPoint"/> equivalent.</summary>
279+
/// <param name="utf8Text">A span container the characters representing the <see cref="IPEndPoint"/> to convert.</param>
280+
/// <param name="provider">An object that provides culture-specific formatting information about <paramref name="utf8Text" />.</param>
281+
/// <param name="result">When this method returns, contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="utf8Text" /> if the conversion succeeded, or default if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten.</param>
282+
/// <returns><c>true</c> if <paramref name="utf8Text" /> was converted successfully; otherwise, false.</returns>
283+
static bool IUtf8SpanParsable<IPEndPoint>.TryParse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider, [NotNullWhen(true)] out IPEndPoint? result) => TryParse(utf8Text, out result);
284+
285+
/// <summary>Returns the string representation of the current instance using the specified format string to define culture-specific formatting.</summary>
286+
/// <param name="format">A standard or custom numeric format string that defines the format of individual elements.</param>
287+
/// <param name="formatProvider">A format provider that supplies culture-specific formatting information.</param>
288+
/// <returns>The string representation of the current instance.</returns>
289+
string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString();
290+
291+
/// <summary>Tries to format the value of the current instance as characters into the provided span of characters.</summary>
292+
/// <param name="destination">When this method returns, this parameter is filled with this instance formatted characters.</param>
293+
/// <param name="charsWritten">When this method returns, the number of bytes that were written in <paramref name="destination"/>.</param>
294+
/// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/>.</returns>
295+
public bool TryFormat(Span<char> destination, out int charsWritten) =>
296+
_address.AddressFamily == AddressFamily.InterNetworkV6 ?
297+
destination.TryWrite(CultureInfo.InvariantCulture, $"[{_address}]:{_port}", out charsWritten) :
298+
destination.TryWrite(CultureInfo.InvariantCulture, $"{_address}:{_port}", out charsWritten);
299+
300+
/// <summary>Tries to format the value of the current instance as UTF-8 bytes into the provided span.</summary>
301+
/// <param name="utf8Destination">When this method returns, this parameter is filled with this instance formatted UTF68 bytes.</param>
302+
/// <param name="bytesWritten">When this method returns, the number of bytes that were written in <paramref name="utf8Destination"/>.</param>
303+
/// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/>.</returns>
304+
public bool TryFormat(Span<byte> utf8Destination, out int bytesWritten) =>
305+
_address.AddressFamily == AddressFamily.InterNetworkV6 ?
306+
Utf8.TryWrite(utf8Destination, CultureInfo.InvariantCulture, $"[{_address}]:{_port}", out bytesWritten) :
307+
Utf8.TryWrite(utf8Destination, CultureInfo.InvariantCulture, $"{_address}:{_port}", out bytesWritten);
308+
309+
/// <summary>Tries to format the value of the current instance as characters into the provided span of characters.</summary>
310+
/// <param name="destination">When this method returns, this parameter is filled with this instance formatted characters.</param>
311+
/// <param name="charsWritten">When this method returns, the number of bytes that were written in <paramref name="destination"/>.</param>
312+
/// <param name="format">A span containing the characters that represent a standard or custom format string that defines the acceptable format for <paramref name="destination"/>.</param>
313+
/// <param name="provider">An optional object that supplies culture-specific formatting information for <paramref name="destination"/>.</param>
314+
/// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/>.</returns>
315+
bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
316+
TryFormat(destination, out charsWritten);
317+
318+
/// <summary>Tries to format the value of the current instance as UTF-8 bytes into the provided span.</summary>
319+
/// <param name="utf8Destination">When this method returns, this parameter is filled with this instance formatted UTF68 bytes.</param>
320+
/// <param name="bytesWritten">When this method returns, the number of bytes that were written in <paramref name="utf8Destination"/>.</param>
321+
/// <param name="format">A span containing the characters that represent a standard or custom format string that defines the acceptable format for <paramref name="utf8Destination"/>.</param>
322+
/// <param name="provider">An optional object that supplies culture-specific formatting information for <paramref name="utf8Destination"/>.</param>
323+
/// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/>.</returns>
324+
bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
325+
TryFormat(utf8Destination, out bytesWritten);
181326
}
182327
}

0 commit comments

Comments
 (0)