Skip to content

Commit a360882

Browse files
authored
Remove LINQ usage for IEnumerable comparisons (#57323)
* Remove LINQ usage for IEnumerable comparisons * Avoid forced cast and add type check
1 parent 6006fd9 commit a360882

File tree

6 files changed

+143
-19
lines changed

6 files changed

+143
-19
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.OpenApi;
5+
6+
internal static class ComparerHelpers
7+
{
8+
internal static bool DictionaryEquals<TKey, TValue>(IDictionary<TKey, TValue> x, IDictionary<TKey, TValue> y, IEqualityComparer<TValue> comparer)
9+
where TKey : notnull
10+
where TValue : notnull
11+
{
12+
if (x is Dictionary<TKey, TValue> xDictionary && y is Dictionary<TKey, TValue> yDictionary)
13+
{
14+
return DictionaryEquals(xDictionary, yDictionary, comparer);
15+
}
16+
17+
if (x.Keys.Count != y.Keys.Count)
18+
{
19+
return false;
20+
}
21+
22+
foreach (var key in x.Keys)
23+
{
24+
if (!y.TryGetValue(key, out var value) || !comparer.Equals(x[key], value))
25+
{
26+
return false;
27+
}
28+
}
29+
30+
return true;
31+
}
32+
33+
// Private method to avoid interface dispatch.
34+
private static bool DictionaryEquals<TKey, TValue>(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y, IEqualityComparer<TValue> comparer)
35+
where TKey : notnull
36+
where TValue : notnull
37+
{
38+
if (x.Keys.Count != y.Keys.Count)
39+
{
40+
return false;
41+
}
42+
43+
foreach (var key in x.Keys)
44+
{
45+
if (!y.TryGetValue(key, out var value) || !comparer.Equals(x[key], value))
46+
{
47+
return false;
48+
}
49+
}
50+
51+
return true;
52+
}
53+
54+
internal static bool ListEquals<T>(IList<T> x, IList<T> y, IEqualityComparer<T> comparer)
55+
{
56+
if (x is List<T> xList && y is List<T> yList)
57+
{
58+
return ListEquals(xList, yList, comparer);
59+
}
60+
61+
if (x.Count != y.Count)
62+
{
63+
return false;
64+
}
65+
66+
for (var i = 0; i < x.Count; i++)
67+
{
68+
if (!comparer.Equals(x[i], y[i]))
69+
{
70+
return false;
71+
}
72+
}
73+
74+
return true;
75+
}
76+
77+
// Private method to avoid interface dispatch.
78+
private static bool ListEquals<T>(List<T> x, List<T> y, IEqualityComparer<T> comparer)
79+
{
80+
if (x.Count != y.Count)
81+
{
82+
return false;
83+
}
84+
85+
for (var i = 0; i < x.Count; i++)
86+
{
87+
if (!comparer.Equals(x[i], y[i]))
88+
{
89+
return false;
90+
}
91+
}
92+
93+
return true;
94+
}
95+
}

src/OpenApi/src/Comparers/OpenApiAnyComparer.cs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33

44
using System.Linq;
55
using Microsoft.OpenApi.Any;
6+
using Microsoft.OpenApi.Interfaces;
67

78
namespace Microsoft.AspNetCore.OpenApi;
89

9-
internal sealed class OpenApiAnyComparer : IEqualityComparer<IOpenApiAny>
10+
internal sealed class OpenApiAnyComparer : IEqualityComparer<IOpenApiAny>, IEqualityComparer<IOpenApiExtension>
1011
{
1112
public static OpenApiAnyComparer Instance { get; } = new OpenApiAnyComparer();
1213

@@ -29,8 +30,8 @@ public bool Equals(IOpenApiAny? x, IOpenApiAny? y)
2930
(x switch
3031
{
3132
OpenApiNull _ => y is OpenApiNull,
32-
OpenApiArray arrayX => y is OpenApiArray arrayY && arrayX.SequenceEqual(arrayY, Instance),
33-
OpenApiObject objectX => y is OpenApiObject objectY && objectX.Keys.Count == objectY.Keys.Count && objectX.Keys.All(key => objectY.TryGetValue(key, out var yValue) && Equals(objectX[key], yValue)),
33+
OpenApiArray arrayX => y is OpenApiArray arrayY && ComparerHelpers.ListEquals(arrayX, arrayY, Instance),
34+
OpenApiObject objectX => y is OpenApiObject objectY && ComparerHelpers.DictionaryEquals(objectX, objectY, Instance),
3435
OpenApiBinary binaryX => y is OpenApiBinary binaryY && binaryX.Value.SequenceEqual(binaryY.Value),
3536
OpenApiInteger integerX => y is OpenApiInteger integerY && integerX.Value == integerY.Value,
3637
OpenApiLong longX => y is OpenApiLong longY && longX.Value == longY.Value,
@@ -78,4 +79,39 @@ public int GetHashCode(IOpenApiAny obj)
7879

7980
return hashCode.ToHashCode();
8081
}
82+
83+
public bool Equals(IOpenApiExtension? x, IOpenApiExtension? y)
84+
{
85+
if (x is null && y is null)
86+
{
87+
return true;
88+
}
89+
90+
if (x is null || y is null)
91+
{
92+
return false;
93+
}
94+
95+
if (object.ReferenceEquals(x, y))
96+
{
97+
return true;
98+
}
99+
100+
if (x is IOpenApiAny openApiAnyX && y is IOpenApiAny openApiAnyY)
101+
{
102+
return Equals(openApiAnyX, openApiAnyY);
103+
}
104+
105+
return x.Equals(y);
106+
}
107+
108+
public int GetHashCode(IOpenApiExtension obj)
109+
{
110+
if (obj is IOpenApiAny any)
111+
{
112+
return GetHashCode(any);
113+
}
114+
115+
return obj.GetHashCode();
116+
}
81117
}

src/OpenApi/src/Comparers/OpenApiDiscriminatorComparer.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
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.Linq;
54
using Microsoft.OpenApi.Models;
65

76
namespace Microsoft.AspNetCore.OpenApi;
@@ -27,7 +26,7 @@ public bool Equals(OpenApiDiscriminator? x, OpenApiDiscriminator? y)
2726

2827
return x.PropertyName == y.PropertyName &&
2928
x.Mapping.Count == y.Mapping.Count &&
30-
x.Mapping.Keys.All(key => y.Mapping.TryGetValue(key, out var yValue) && x.Mapping[key] == yValue);
29+
ComparerHelpers.DictionaryEquals(x.Mapping, y.Mapping, StringComparer.Ordinal);
3130
}
3231

3332
public int GetHashCode(OpenApiDiscriminator obj)

src/OpenApi/src/Comparers/OpenApiExternalDocsComparer.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
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.Linq;
54
using Microsoft.OpenApi.Models;
65

76
namespace Microsoft.AspNetCore.OpenApi;
@@ -27,8 +26,7 @@ public bool Equals(OpenApiExternalDocs? x, OpenApiExternalDocs? y)
2726

2827
return x.Description == y.Description &&
2928
x.Url == y.Url &&
30-
x.Extensions.Count == y.Extensions.Count
31-
&& x.Extensions.Keys.All(k => y.Extensions.TryGetValue(k, out var yValue) && yValue == x.Extensions[k]);
29+
ComparerHelpers.DictionaryEquals(x.Extensions, y.Extensions, OpenApiAnyComparer.Instance);
3230
}
3331

3432
public int GetHashCode(OpenApiExternalDocs obj)

src/OpenApi/src/Comparers/OpenApiSchemaComparer.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
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.Linq;
5-
using Microsoft.OpenApi.Any;
64
using Microsoft.OpenApi.Models;
75

86
namespace Microsoft.AspNetCore.OpenApi;
@@ -31,22 +29,22 @@ public bool Equals(OpenApiSchema? x, OpenApiSchema? y)
3129
x.Type == y.Type &&
3230
x.Format == y.Format &&
3331
SchemaIdEquals(x, y) &&
34-
x.Properties.Keys.All(k => y.Properties.TryGetValue(k, out var yValue) && Instance.Equals(x.Properties[k], yValue)) &&
32+
ComparerHelpers.DictionaryEquals(x.Properties, y.Properties, Instance) &&
3533
OpenApiDiscriminatorComparer.Instance.Equals(x.Discriminator, y.Discriminator) &&
3634
Instance.Equals(x.AdditionalProperties, y.AdditionalProperties) &&
3735
x.AdditionalPropertiesAllowed == y.AdditionalPropertiesAllowed &&
38-
x.AllOf.SequenceEqual(y.AllOf, Instance) &&
39-
x.AnyOf.SequenceEqual(y.AnyOf, Instance) &&
36+
ComparerHelpers.ListEquals(x.AllOf, y.AllOf, Instance) &&
37+
ComparerHelpers.ListEquals(x.AnyOf, y.AnyOf, Instance) &&
4038
x.Deprecated == y.Deprecated &&
4139
OpenApiAnyComparer.Instance.Equals(x.Default, y.Default) &&
4240
x.Description == y.Description &&
4341
OpenApiAnyComparer.Instance.Equals(x.Example, y.Example) &&
4442
x.ExclusiveMaximum == y.ExclusiveMaximum &&
4543
x.ExclusiveMinimum == y.ExclusiveMinimum &&
4644
x.Extensions.Count == y.Extensions.Count &&
47-
x.Extensions.Keys.All(k => y.Extensions.TryGetValue(k, out var yValue) && x.Extensions[k] is IOpenApiAny anyX && yValue is IOpenApiAny anyY && OpenApiAnyComparer.Instance.Equals(anyX, anyY)) &&
45+
ComparerHelpers.DictionaryEquals(x.Extensions, y.Extensions, OpenApiAnyComparer.Instance) &&
4846
OpenApiExternalDocsComparer.Instance.Equals(x.ExternalDocs, y.ExternalDocs) &&
49-
x.Enum.SequenceEqual(y.Enum, OpenApiAnyComparer.Instance) &&
47+
ComparerHelpers.ListEquals(x.Enum, y.Enum, OpenApiAnyComparer.Instance) &&
5048
Instance.Equals(x.Items, y.Items) &&
5149
x.Title == y.Title &&
5250
x.Maximum == y.Maximum &&
@@ -58,7 +56,7 @@ public bool Equals(OpenApiSchema? x, OpenApiSchema? y)
5856
x.MinLength == y.MinLength &&
5957
x.MinProperties == y.MinProperties &&
6058
x.MultipleOf == y.MultipleOf &&
61-
x.OneOf.SequenceEqual(y.OneOf, Instance) &&
59+
ComparerHelpers.ListEquals(x.OneOf, y.OneOf, Instance) &&
6260
Instance.Equals(x.Not, y.Not) &&
6361
x.Nullable == y.Nullable &&
6462
x.Pattern == y.Pattern &&

src/OpenApi/src/Comparers/OpenApiXmlComparer.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
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.Linq;
54
using Microsoft.OpenApi.Models;
65

76
namespace Microsoft.AspNetCore.OpenApi;
@@ -30,8 +29,7 @@ public bool Equals(OpenApiXml? x, OpenApiXml? y)
3029
x.Prefix == y.Prefix &&
3130
x.Attribute == y.Attribute &&
3231
x.Wrapped == y.Wrapped &&
33-
x.Extensions.Count == y.Extensions.Count
34-
&& x.Extensions.Keys.All(k => y.Extensions.TryGetValue(k, out var yValue) && yValue == x.Extensions[k]);
32+
ComparerHelpers.DictionaryEquals(x.Extensions, y.Extensions, OpenApiAnyComparer.Instance);
3533
}
3634

3735
public int GetHashCode(OpenApiXml obj)

0 commit comments

Comments
 (0)