Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 42 additions & 36 deletions src/libraries/System.Private.Uri/src/System/Uri.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace System
{
[Serializable]
[System.Runtime.CompilerServices.TypeForwardedFrom("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
public partial class Uri : ISpanFormattable, ISerializable
public partial class Uri : ISpanFormattable, IEquatable<Uri>, ISerializable
{
public static readonly string UriSchemeFile = UriParser.FileUri.SchemeName;
public static readonly string UriSchemeFtp = UriParser.FtpUri.SchemeName;
Expand Down Expand Up @@ -1674,20 +1674,6 @@ string IFormattable.ToString(string? format, IFormatProvider? formatProvider) =>
return !uri1.Equals(uri2);
}

//
// Equals
//
// Overrides default function (in Object class)
//
// Assumes:
// <comparand> is an object of class Uri or String
//
// Returns:
// true if objects have the same value, else false
//
// Throws:
// Nothing
//
public override bool Equals([NotNullWhen(true)] object? comparand)
{
if (comparand is null)
Expand All @@ -1700,12 +1686,12 @@ public override bool Equals([NotNullWhen(true)] object? comparand)
return true;
}

Uri? obj = comparand as Uri;
Uri? other = comparand as Uri;

// we allow comparisons of Uri and String objects only. If a string
// is passed, convert to Uri. This is inefficient, but allows us to
// canonicalize the comparand, making comparison possible
if (obj is null)
if (other is null)
{
if (DisablePathAndQueryCanonicalization)
return false;
Expand All @@ -1716,28 +1702,48 @@ public override bool Equals([NotNullWhen(true)] object? comparand)
if (ReferenceEquals(s, OriginalString))
return true;

if (!TryCreate(s, UriKind.RelativeOrAbsolute, out obj))
if (!TryCreate(s, UriKind.RelativeOrAbsolute, out other))
return false;
}

if (DisablePathAndQueryCanonicalization != obj.DisablePathAndQueryCanonicalization)
return Equals(other);
}

/// <summary>
/// Compares two <see cref="Uri"/> instances for equality.
/// </summary>
/// <param name="other">The <see cref="Uri"/> to compare to this instance.</param>
/// <returns><see langword="true"/> if the two instances represent the same URI; otherwise, <see langword="false"/>.</returns>
public bool Equals([NotNullWhen(true)] Uri? other)
{
if (other is null)
{
return false;
}

if (ReferenceEquals(this, other))
{
return true;
}

if (DisablePathAndQueryCanonicalization != other.DisablePathAndQueryCanonicalization)
return false;

if (ReferenceEquals(OriginalString, obj.OriginalString))
if (ReferenceEquals(OriginalString, other.OriginalString))
{
return true;
}

if (IsAbsoluteUri != obj.IsAbsoluteUri)
if (IsAbsoluteUri != other.IsAbsoluteUri)
return false;

if (IsNotAbsoluteUri)
return OriginalString.Equals(obj.OriginalString);
return OriginalString.Equals(other.OriginalString);

if (NotAny(Flags.AllUriInfoSet) || obj.NotAny(Flags.AllUriInfoSet))
if (NotAny(Flags.AllUriInfoSet) || other.NotAny(Flags.AllUriInfoSet))
{
// Try raw compare for _strings as the last chance to keep the working set small
if (string.Equals(_string, obj._string, IsUncOrDosPath ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
if (string.Equals(_string, other._string, IsUncOrDosPath ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
{
return true;
}
Expand All @@ -1746,20 +1752,20 @@ public override bool Equals([NotNullWhen(true)] object? comparand)
// Note that equality test will bring the working set of both
// objects up to creation of _info.MoreInfo member
EnsureUriInfo();
obj.EnsureUriInfo();
other.EnsureUriInfo();

if (!UserDrivenParsing && !obj.UserDrivenParsing && Syntax!.IsSimple && obj.Syntax.IsSimple)
if (!UserDrivenParsing && !other.UserDrivenParsing && Syntax!.IsSimple && other.Syntax.IsSimple)
{
// Optimization of canonical DNS names by avoiding host string creation.
// Note there could be explicit ports specified that would invalidate path offsets
if (InFact(Flags.CanonicalDnsHost) && obj.InFact(Flags.CanonicalDnsHost))
if (InFact(Flags.CanonicalDnsHost) && other.InFact(Flags.CanonicalDnsHost))
{
int i1 = _info.Offset.Host;
int end1 = _info.Offset.Path;

int i2 = obj._info.Offset.Host;
int end2 = obj._info.Offset.Path;
string str = obj._string;
int i2 = other._info.Offset.Host;
int end2 = other._info.Offset.Path;
string str = other._string;
//Taking the shortest part
if (end1 - i1 > end2 - i2)
{
Expand Down Expand Up @@ -1794,14 +1800,14 @@ public override bool Equals([NotNullWhen(true)] object? comparand)
else
{
EnsureHostString(false);
obj.EnsureHostString(false);
if (!_info.Host!.Equals(obj._info.Host))
other.EnsureHostString(false);
if (!_info.Host!.Equals(other._info.Host))
{
return false;
}
}

if (Port != obj.Port)
if (Port != other.Port)
{
return false;
}
Expand All @@ -1811,21 +1817,21 @@ public override bool Equals([NotNullWhen(true)] object? comparand)
// We should consider reducing the overall working set by not caching some other properties mentioned in MoreInfo

MoreInfo selfInfo = _info.MoreInfo;
MoreInfo otherInfo = obj._info.MoreInfo;
MoreInfo otherInfo = other._info.MoreInfo;

// Fragment AND UserInfo (for non-mailto URIs) are ignored
UriComponents components = UriComponents.HttpRequestUrl;

if (_syntax.InFact(UriSyntaxFlags.MailToLikeUri))
{
if (!obj._syntax.InFact(UriSyntaxFlags.MailToLikeUri))
if (!other._syntax.InFact(UriSyntaxFlags.MailToLikeUri))
return false;

components |= UriComponents.UserInfo;
}

string selfUrl = selfInfo.RemoteUrl ??= GetParts(components, UriFormat.SafeUnescaped);
string otherUrl = otherInfo.RemoteUrl ??= obj.GetParts(components, UriFormat.SafeUnescaped);
string otherUrl = otherInfo.RemoteUrl ??= other.GetParts(components, UriFormat.SafeUnescaped);

// if IsUncOrDosPath is true then we ignore case in the path comparison
return string.Equals(selfUrl, otherUrl, IsUncOrDosPath ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
Expand Down
3 changes: 2 additions & 1 deletion src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16225,7 +16225,7 @@ public partial class NewsStyleUriParser : System.UriParser
{
public NewsStyleUriParser() { }
}
public partial class Uri : System.IFormattable, System.ISpanFormattable, System.Runtime.Serialization.ISerializable
public partial class Uri : System.IFormattable, System.ISpanFormattable, System.IEquatable<Uri>, System.Runtime.Serialization.ISerializable
{
public static readonly string SchemeDelimiter;
public static readonly string UriSchemeFile;
Expand Down Expand Up @@ -16286,6 +16286,7 @@ protected virtual void Canonicalize() { }
protected virtual void CheckSecurity() { }
public static int Compare(System.Uri? uri1, System.Uri? uri2, System.UriComponents partsToCompare, System.UriFormat compareFormat, System.StringComparison comparisonType) { throw null; }
public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? comparand) { throw null; }
public bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] Uri? other) { throw null; }
[System.ObsoleteAttribute("Uri.Escape has been deprecated and is not supported.")]
protected virtual void Escape() { }
public static string EscapeDataString(string stringToEscape) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,20 +395,28 @@ public void EqualsTest(Uri uri1, object obj, bool expected)
{
Uri uri2 = obj as Uri;

if (uri1 != null)
if (uri1 is not null)
{
Assert.Equal(expected, uri1.Equals(obj));

if (uri2 != null && expected)
if (uri2 is not null)
{
Assert.Equal(uri1.GetHashCode(), uri2.GetHashCode());
Assert.Equal(expected, uri1.Equals(uri2));
Assert.Equal(expected, uri2.Equals(uri1));

if (expected)
{
Assert.Equal(uri1.GetHashCode(), uri2.GetHashCode());
}
}
}

if (!(obj is string))
if (obj is not string)
{
Assert.Equal(expected, uri1 == uri2);
Assert.Equal(expected, uri2 == uri1);
Assert.Equal(!expected, uri1 != uri2);
Assert.Equal(!expected, uri2 != uri1);
}
}

Expand Down