@@ -17,7 +17,7 @@ namespace System
1717{
1818 [ Serializable ]
1919 [ System . Runtime . CompilerServices . TypeForwardedFrom ( "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" ) ]
20- public partial class Uri : ISerializable
20+ public partial class Uri : ISpanFormattable , ISerializable
2121 {
2222 public static readonly string UriSchemeFile = UriParser . FileUri . SchemeName ;
2323 public static readonly string UriSchemeFtp = UriParser . FtpUri . SchemeName ;
@@ -46,7 +46,7 @@ public partial class Uri : ISerializable
4646 // or idn is on and we have unicode host or idn host
4747 // In that case, this string is normalized, stripped of bidi chars, and validated
4848 // with char limits
49- private string _string = null ! ; // initialized early in ctor via a helper
49+ private string _string ;
5050
5151 // untouched user string if string has unicode with iri on or unicode/idn host with idn on
5252 private string _originalUnicodeString = null ! ; // initialized in ctor via helper
@@ -319,6 +319,7 @@ private static bool StaticInFact(Flags allFlags, Flags checkFlags)
319319 return ( allFlags & checkFlags ) != 0 ;
320320 }
321321
322+ [ MemberNotNull ( nameof ( _info ) ) ]
322323 private UriInfo EnsureUriInfo ( )
323324 {
324325 Flags cF = _flags ;
@@ -338,6 +339,7 @@ private void EnsureParseRemaining()
338339 }
339340 }
340341
342+ [ MemberNotNull ( nameof ( _info ) ) ]
341343 private void EnsureHostString ( bool allowDnsOptimization )
342344 {
343345 UriInfo info = EnsureUriInfo ( ) ;
@@ -497,6 +499,7 @@ protected void GetObjectData(SerializationInfo serializationInfo, StreamingConte
497499 }
498500 }
499501
502+ [ MemberNotNull ( nameof ( _string ) ) ]
500503 private void CreateUri ( Uri baseUri , string ? relativeUri , bool dontEscape )
501504 {
502505 DebugAssertInCtor ( ) ;
@@ -1173,7 +1176,7 @@ public string IdnHost
11731176 {
11741177 EnsureHostString ( false ) ;
11751178
1176- string host = _info ! . Host ! ;
1179+ string host = _info . Host ! ;
11771180
11781181 Flags hostType = HostType ;
11791182 if ( hostType == Flags . DnsHostType )
@@ -1538,8 +1541,6 @@ public override int GetHashCode()
15381541 //
15391542 // ToString
15401543 //
1541- // The better implementation would be just
1542- //
15431544 private const UriFormat V1ToStringUnescape = ( UriFormat ) 0x7FFF ;
15441545
15451546 public override string ToString ( )
@@ -1550,16 +1551,93 @@ public override string ToString()
15501551 }
15511552
15521553 EnsureUriInfo ( ) ;
1553- if ( _info . String is null )
1554+ return _info . String ??=
1555+ _syntax . IsSimple ?
1556+ GetComponentsHelper ( UriComponents . AbsoluteUri , V1ToStringUnescape ) :
1557+ GetParts ( UriComponents . AbsoluteUri , UriFormat . SafeUnescaped ) ;
1558+ }
1559+
1560+ /// <summary>
1561+ /// Attempts to format a canonical string representation for the <see cref="Uri"/> instance into the specified span.
1562+ /// </summary>
1563+ /// <param name="destination">The span into which to write this instance's value formatted as a span of characters.</param>
1564+ /// <param name="charsWritten">When this method returns, contains the number of characters that were written in <paramref name="destination"/>.</param>
1565+ /// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/>.</returns>
1566+ public bool TryFormat ( Span < char > destination , out int charsWritten )
1567+ {
1568+ ReadOnlySpan < char > result ;
1569+
1570+ if ( _syntax is null )
15541571 {
1555- if ( _syntax . IsSimple )
1556- _info . String = GetComponentsHelper ( UriComponents . AbsoluteUri , V1ToStringUnescape ) ;
1572+ result = _string ;
1573+ }
1574+ else
1575+ {
1576+ EnsureUriInfo ( ) ;
1577+ if ( _info . String is not null )
1578+ {
1579+ result = _info . String ;
1580+ }
15571581 else
1558- _info . String = GetParts ( UriComponents . AbsoluteUri , UriFormat . SafeUnescaped ) ;
1582+ {
1583+ UriFormat uriFormat = V1ToStringUnescape ;
1584+ if ( ! _syntax . IsSimple )
1585+ {
1586+ if ( IsNotAbsoluteUri )
1587+ {
1588+ throw new InvalidOperationException ( SR . net_uri_NotAbsolute ) ;
1589+ }
1590+
1591+ if ( UserDrivenParsing )
1592+ {
1593+ throw new InvalidOperationException ( SR . Format ( SR . net_uri_UserDrivenParsing , GetType ( ) ) ) ;
1594+ }
1595+
1596+ if ( DisablePathAndQueryCanonicalization )
1597+ {
1598+ throw new InvalidOperationException ( SR . net_uri_GetComponentsCalledWhenCanonicalizationDisabled ) ;
1599+ }
1600+
1601+ uriFormat = UriFormat . SafeUnescaped ;
1602+ }
1603+
1604+ EnsureParseRemaining ( ) ;
1605+ EnsureHostString ( allowDnsOptimization : true ) ;
1606+
1607+ ushort nonCanonical = ( ushort ) ( ( ushort ) _flags & ( ushort ) Flags . CannotDisplayCanonical ) ;
1608+ if ( ( ( _flags & ( Flags . ShouldBeCompressed | Flags . FirstSlashAbsent | Flags . BackslashInPath ) ) != 0 ) ||
1609+ ( IsDosPath && _string [ _info . Offset . Path + SecuredPathIndex - 1 ] == '|' ) ) // A rare case of c|\
1610+ {
1611+ nonCanonical |= ( ushort ) Flags . PathNotCanonical ;
1612+ }
1613+
1614+ if ( ( ( ushort ) UriComponents . AbsoluteUri & nonCanonical ) != 0 )
1615+ {
1616+ return TryRecreateParts ( destination , out charsWritten , UriComponents . AbsoluteUri , nonCanonical , uriFormat ) ;
1617+ }
1618+
1619+ result = _string . AsSpan ( _info . Offset . Scheme , _info . Offset . End - _info . Offset . Scheme ) ;
1620+ }
1621+ }
1622+
1623+ if ( result . TryCopyTo ( destination ) )
1624+ {
1625+ charsWritten = result . Length ;
1626+ return true ;
15591627 }
1560- return _info . String ;
1628+
1629+ charsWritten = 0 ;
1630+ return false ;
15611631 }
15621632
1633+ /// <inheritdoc/>
1634+ bool ISpanFormattable . TryFormat ( Span < char > destination , out int charsWritten , ReadOnlySpan < char > format , IFormatProvider ? provider ) =>
1635+ TryFormat ( destination , out charsWritten ) ;
1636+
1637+ /// <inheritdoc/>
1638+ string IFormattable . ToString ( string ? format , IFormatProvider ? formatProvider ) =>
1639+ ToString ( ) ;
1640+
15631641 public static bool operator == ( Uri ? uri1 , Uri ? uri2 )
15641642 {
15651643 if ( ReferenceEquals ( uri1 , uri2 ) )
@@ -1664,7 +1742,7 @@ public override bool Equals([NotNullWhen(true)] object? comparand)
16641742 EnsureUriInfo ( ) ;
16651743 obj . EnsureUriInfo ( ) ;
16661744
1667- if ( ! UserDrivenParsing && ! obj . UserDrivenParsing && Syntax ! . IsSimple && obj . Syntax ! . IsSimple )
1745+ if ( ! UserDrivenParsing && ! obj . UserDrivenParsing && Syntax ! . IsSimple && obj . Syntax . IsSimple )
16681746 {
16691747 // Optimization of canonical DNS names by avoiding host string creation.
16701748 // Note there could be explicit ports specified that would invalidate path offsets
@@ -2580,7 +2658,7 @@ private string GetEscapedParts(UriComponents uriParts)
25802658 }
25812659 }
25822660
2583- return ReCreateParts ( uriParts , nonCanonical , UriFormat . UriEscaped ) ;
2661+ return RecreateParts ( uriParts , nonCanonical , UriFormat . UriEscaped ) ;
25842662 }
25852663
25862664 private string GetUnescapedParts ( UriComponents uriParts , UriFormat formatAs )
@@ -2615,19 +2693,46 @@ private string GetUnescapedParts(UriComponents uriParts, UriFormat formatAs)
26152693 }
26162694 }
26172695
2618- return ReCreateParts ( uriParts , nonCanonical , formatAs ) ;
2696+ return RecreateParts ( uriParts , nonCanonical , formatAs ) ;
26192697 }
26202698
2621- private string ReCreateParts ( UriComponents parts , ushort nonCanonical , UriFormat formatAs )
2699+ private string RecreateParts ( UriComponents parts , ushort nonCanonical , UriFormat formatAs )
26222700 {
2623- EnsureHostString ( false ) ;
2701+ EnsureHostString ( allowDnsOptimization : false ) ;
26242702
26252703 string str = _string ;
26262704
26272705 var dest = str . Length <= StackallocThreshold
26282706 ? new ValueStringBuilder ( stackalloc char [ StackallocThreshold ] )
26292707 : new ValueStringBuilder ( str . Length ) ;
26302708
2709+ scoped ReadOnlySpan < char > result = RecreateParts ( ref dest , str , parts , nonCanonical , formatAs ) ;
2710+
2711+ string s = result . ToString ( ) ;
2712+ dest . Dispose ( ) ;
2713+ return s ;
2714+ }
2715+
2716+ private bool TryRecreateParts ( scoped Span < char > span , out int charsWritten , UriComponents parts , ushort nonCanonical , UriFormat formatAs )
2717+ {
2718+ EnsureHostString ( allowDnsOptimization : false ) ;
2719+
2720+ string str = _string ;
2721+
2722+ var dest = str . Length <= StackallocThreshold
2723+ ? new ValueStringBuilder ( stackalloc char [ StackallocThreshold ] )
2724+ : new ValueStringBuilder ( str . Length ) ;
2725+
2726+ scoped ReadOnlySpan < char > result = RecreateParts ( ref dest , str , parts , nonCanonical , formatAs ) ;
2727+
2728+ bool copied = result . TryCopyTo ( span ) ;
2729+ charsWritten = copied ? result . Length : 0 ;
2730+ dest . Dispose ( ) ;
2731+ return copied ;
2732+ }
2733+
2734+ private ReadOnlySpan < char > RecreateParts ( scoped ref ValueStringBuilder dest , string str , UriComponents parts , ushort nonCanonical , UriFormat formatAs )
2735+ {
26312736 //Scheme and slashes
26322737 if ( ( parts & UriComponents . Scheme ) != 0 )
26332738 {
@@ -2778,9 +2883,7 @@ private string ReCreateParts(UriComponents parts, ushort nonCanonical, UriFormat
27782883 offset = 0 ;
27792884 }
27802885
2781- string result = dest . AsSpan ( offset ) . ToString ( ) ;
2782- dest . Dispose ( ) ;
2783- return result ;
2886+ return dest . AsSpan ( offset ) ;
27842887 }
27852888 }
27862889
@@ -2860,9 +2963,9 @@ private string ReCreateParts(UriComponents parts, ushort nonCanonical, UriFormat
28602963 ref dest , '#' , c_DummyChar , c_DummyChar ,
28612964 mode , _syntax , isQuery : false ) ;
28622965 }
2863- AfterFragment :
28642966
2865- return dest . ToString ( ) ;
2967+ AfterFragment :
2968+ return dest . AsSpan ( ) ;
28662969 }
28672970
28682971 //
0 commit comments