Skip to content

Commit 84028c7

Browse files
Chris Martinezcommonsensesoftware
authored andcommitted
Add performance and code analysis fixes
1 parent 35d9016 commit 84028c7

24 files changed

+340
-148
lines changed

src/Common/CollectionExtensions.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ namespace Microsoft.AspNetCore.Mvc
1818

1919
static partial class CollectionExtensions
2020
{
21-
#if NETAPPCORE3_1
22-
internal static bool TryGetValue<TKey, TValue>( this IDictionary<TKey, object?> dictionary, TKey key, [NotNullWhen(true)] out TValue value ) where TKey : notnull
23-
#else
21+
#if WEBAPI
2422
internal static bool TryGetValue<TKey, TValue>( this IDictionary<TKey, object?> dictionary, TKey key, out TValue value ) where TKey : notnull
23+
#else
24+
internal static bool TryGetValue<TKey, TValue>( this IDictionary<TKey, object?> dictionary, TKey key, [NotNullWhen( true )] out TValue value ) where TKey : notnull
2525
#endif
2626
{
2727
if ( dictionary.TryGetValue( key, out var val ) && val is TValue v )
@@ -34,7 +34,7 @@ internal static bool TryGetValue<TKey, TValue>( this IDictionary<TKey, object?>
3434
return false;
3535
}
3636

37-
internal static List<T> AsList<T>( this IEnumerable<T> sequence ) => ( sequence as List<T> ) ?? new List<T>( sequence );
37+
internal static List<T> AsList<T>( this IEnumerable<T> sequence ) => ( sequence as List<T> ) ?? sequence.ToList();
3838

3939
internal static IReadOnlyList<T> ToSortedReadOnlyList<T>( this IEnumerable<T> sequence ) where T : IComparable<T>
4040
{
@@ -59,15 +59,18 @@ internal static void AddRange<T>( this ICollection<T> collection, IEnumerable<T>
5959

6060
internal static string? EnsureZeroOrOneApiVersions( this ICollection<string> apiVersions )
6161
{
62-
if ( apiVersions.Count < 2 )
62+
switch ( apiVersions.Count )
6363
{
64-
return apiVersions.SingleOrDefault();
64+
case 0:
65+
return default;
66+
case 1:
67+
var values = new string[1];
68+
apiVersions.CopyTo( values, 0 );
69+
return values[0];
70+
default:
71+
var message = Format( InvariantCulture, SR.MultipleDifferentApiVersionsRequested, Join( ", ", apiVersions ) );
72+
throw new AmbiguousApiVersionException( message, apiVersions );
6573
}
66-
67-
var requestedVersions = Join( ", ", apiVersions.OrderBy( v => v ) );
68-
var message = Format( InvariantCulture, SR.MultipleDifferentApiVersionsRequested, requestedVersions );
69-
70-
throw new AmbiguousApiVersionException( message, apiVersions.OrderBy( v => v ) );
7174
}
7275

7376
internal static void UnionWith<T>( this ICollection<T> collection, IEnumerable<T> other )

src/Common/Versioning/AmbiguousApiVersionException.cs

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,40 @@ namespace Microsoft.Web.Http.Versioning
44
namespace Microsoft.AspNetCore.Mvc.Versioning
55
#endif
66
{
7-
// disable warnings for false positives targeting netstandard2.0
8-
#pragma warning disable CA1032 // Implement standard exception constructors
9-
#pragma warning disable CA2235 // Mark all non-serializable fields
10-
117
using System;
128
using System.Collections.Generic;
139
using System.Linq;
14-
#if NET451 || WEBAPI
10+
using System.Runtime.CompilerServices;
1511
using System.Runtime.Serialization;
16-
#endif
12+
using static System.Runtime.CompilerServices.MethodImplOptions;
1713

1814
/// <summary>
1915
/// Represents the exception thrown when multiple, different API versions specified in a single request.
2016
/// </summary>
21-
#if NET451 || WEBAPI
2217
[Serializable]
23-
#endif
2418
public class AmbiguousApiVersionException : Exception
2519
{
2620
readonly string[] apiVersions;
2721

22+
/// <summary>
23+
/// Initializes a new instance of the <see cref="AmbiguousApiVersionException"/> class.
24+
/// </summary>
25+
public AmbiguousApiVersionException() => apiVersions = EmptyArray();
26+
27+
/// <summary>
28+
/// Initializes a new instance of the <see cref="AmbiguousApiVersionException"/> class.
29+
/// </summary>
30+
/// <param name="message">The associated error message.</param>
31+
public AmbiguousApiVersionException( string message ) : base( message ) => apiVersions = EmptyArray();
32+
33+
/// <summary>
34+
/// Initializes a new instance of the <see cref="AmbiguousApiVersionException"/> class.
35+
/// </summary>
36+
/// <param name="message">The associated error message.</param>
37+
/// <param name="innerException">The inner <see cref="Exception">exception</see> that caused the current exception, if any.</param>
38+
public AmbiguousApiVersionException( string message, Exception innerException )
39+
: base( message, innerException ) => apiVersions = EmptyArray();
40+
2841
/// <summary>
2942
/// Initializes a new instance of the <see cref="AmbiguousApiVersionException"/> class.
3043
/// </summary>
@@ -47,14 +60,14 @@ public AmbiguousApiVersionException( string message, IEnumerable<string> apiVers
4760
/// </summary>
4861
/// <value>A <see cref="IReadOnlyList{T}">read-only list</see> of unparsed, ambiguous API versions.</value>
4962
public IReadOnlyList<string> ApiVersions => apiVersions;
50-
#if NET451 || WEBAPI
63+
5164
/// <summary>
5265
/// Initializes a new instance of the <see cref="AmbiguousApiVersionException"/> class.
5366
/// </summary>
5467
/// <param name="info">The <see cref="SerializationInfo">serialization info</see> the exception is being deserialized with.</param>
5568
/// <param name="context">The <see cref="StreamingContext">streaming context</see> the exception is being deserialized from.</param>
5669
protected AmbiguousApiVersionException( SerializationInfo info, StreamingContext context )
57-
: base( info, context ) => apiVersions = (string[]) info.GetValue( nameof( apiVersions ), typeof( string[] ) );
70+
: base( info, context ) => apiVersions = (string[]) info.GetValue( nameof( apiVersions ), typeof( string[] ) )!;
5871

5972
/// <summary>
6073
/// Gets information about the exception being serialized.
@@ -66,6 +79,12 @@ public override void GetObjectData( SerializationInfo info, StreamingContext con
6679
base.GetObjectData( info, context );
6780
info.AddValue( nameof( apiVersions ), apiVersions );
6881
}
82+
83+
[MethodImpl( AggressiveInlining )]
84+
#if WEBAPI
85+
static string[] EmptyArray() => new string[0];
86+
#else
87+
static string[] EmptyArray() => Array.Empty<string>();
6988
#endif
7089
}
7190
}

src/Common/Versioning/Conventions/ActionApiVersionConventionBuilderCollection.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ protected internal virtual ActionApiVersionConventionBuilder GetOrAdd( MethodInf
5959
/// <param name="actionMethod">The controller action method to get the convention builder for.</param>
6060
/// <param name="actionBuilder">The <see cref="ActionApiVersionConventionBuilder">controller action convention builder</see> or <c>null</c>.</param>
6161
/// <returns>True if the <paramref name="actionBuilder">action builder</paramref> is successfully retrieved; otherwise, false.</returns>
62-
#if NETCOREAPP3_1
63-
public virtual bool TryGetValue( MethodInfo? actionMethod, [NotNullWhen( true )] out ActionApiVersionConventionBuilder? actionBuilder )
64-
#else
62+
#if WEBAPI
6563
public virtual bool TryGetValue( MethodInfo? actionMethod, out ActionApiVersionConventionBuilder? actionBuilder )
64+
#else
65+
public virtual bool TryGetValue( MethodInfo? actionMethod, [NotNullWhen( true )] out ActionApiVersionConventionBuilder? actionBuilder )
6666
#endif
6767
{
6868
if ( actionMethod == null )

src/Common/Versioning/Conventions/ActionApiVersionConventionBuilderCollection{T}.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ protected internal virtual ActionApiVersionConventionBuilder<T> GetOrAdd( Method
5858
/// <param name="actionMethod">The controller action method to get the convention builder for.</param>
5959
/// <param name="actionBuilder">The <see cref="ActionApiVersionConventionBuilder{T}">controller action convention builder</see> or <c>null</c>.</param>
6060
/// <returns>True if the <paramref name="actionBuilder">action builder</paramref> is successfully retrieved; otherwise, false.</returns>
61-
#if NETCOREAPP3_1
62-
public virtual bool TryGetValue( MethodInfo? actionMethod, [NotNullWhen( true )] out ActionApiVersionConventionBuilder<T>? actionBuilder )
63-
#else
61+
#if WEBAPI
6462
public virtual bool TryGetValue( MethodInfo? actionMethod, out ActionApiVersionConventionBuilder<T>? actionBuilder )
63+
#else
64+
public virtual bool TryGetValue( MethodInfo? actionMethod, [NotNullWhen( true )] out ActionApiVersionConventionBuilder<T>? actionBuilder )
6565
#endif
6666
{
6767
if ( actionMethod == null )

src/Common/Versioning/HeaderApiVersionReader.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,14 @@ public virtual void AddParameters( IApiVersionParameterDescriptionContext contex
5050
throw new ArgumentNullException( nameof( context ) );
5151
}
5252

53-
foreach ( var name in HeaderNames )
53+
var count = HeaderNames.Count;
54+
var names = new string[count];
55+
56+
HeaderNames.CopyTo( names, 0 );
57+
58+
for ( var i = 0; i < count; i++ )
5459
{
55-
context.AddParameter( name, Header );
60+
context.AddParameter( names[i], Header );
5661
}
5762
}
5863
}

src/Common/Versioning/MediaTypeApiVersionReader.cs

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace Microsoft.AspNetCore.Mvc.Versioning
55
#endif
66
{
77
#if WEBAPI
8+
using Microsoft.Web.Http;
89
using Microsoft.Web.Http.Routing;
910
#else
1011
using Microsoft.AspNetCore.Mvc.Routing;
@@ -20,6 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Versioning
2021
using MediaTypeWithQualityHeaderValue = Microsoft.Net.Http.Headers.MediaTypeHeaderValue;
2122
#endif
2223
using static ApiVersionParameterLocation;
24+
using static System.StringComparison;
2325

2426
/// <summary>
2527
/// Represents a service API version reader that reads the value from a media type HTTP header in the request.
@@ -47,34 +49,48 @@ public partial class MediaTypeApiVersionReader : IApiVersionReader
4749
/// <summary>
4850
/// Reads the requested API version from the HTTP Accept header.
4951
/// </summary>
50-
/// <param name="accept">The <see cref="IEnumerable{T}">sequence</see> of Accept
52+
/// <param name="accept">The <see cref="ICollection{T}">collection</see> of Accept
5153
/// <see cref="MediaTypeWithQualityHeaderValue">headers</see> to read from.</param>
5254
/// <returns>The API version read or <c>null</c>.</returns>
5355
/// <remarks>The default implementation will return the first defined API version ranked by the media type
5456
/// quality parameter.</remarks>
55-
protected virtual string? ReadAcceptHeader( IEnumerable<MediaTypeWithQualityHeaderValue> accept )
57+
protected virtual string? ReadAcceptHeader( ICollection<MediaTypeWithQualityHeaderValue> accept )
5658
{
57-
var comparer = StringComparer.OrdinalIgnoreCase;
58-
var contentTypes = from entry in accept
59-
orderby entry.Quality descending
60-
group entry by entry.MediaType;
59+
if ( accept == null )
60+
{
61+
throw new ArgumentNullException( nameof( accept ) );
62+
}
6163

62-
foreach ( var contentType in contentTypes )
64+
var count = accept.Count;
65+
66+
if ( count == 0 )
6367
{
64-
foreach ( var entry in contentType )
68+
return default;
69+
}
70+
71+
var mediaTypes = accept.ToArray();
72+
73+
Array.Sort( mediaTypes, ByQualityDescending );
74+
75+
for ( var i = 0; i < count; i++ )
76+
{
77+
#if WEBAPI
78+
var parameters = mediaTypes[i].Parameters.ToArray();
79+
var paramCount = parameters.Length;
80+
#else
81+
var parameters = mediaTypes[i].Parameters;
82+
var paramCount = parameters.Count;
83+
#endif
84+
for ( var j = 0; j < paramCount; j++ )
6585
{
66-
foreach ( var parameter in entry.Parameters )
86+
var parameter = parameters[j];
87+
88+
if ( parameter.Name.Equals( ParameterName, OrdinalIgnoreCase ) )
6789
{
6890
#if WEBAPI
69-
if ( comparer.Equals( parameter.Name, ParameterName ) )
70-
{
71-
return parameter.Value;
72-
}
91+
return parameter.Value;
7392
#else
74-
if ( comparer.Equals( parameter.Name.Value, ParameterName ) )
75-
{
76-
return parameter.Value.Value;
77-
}
93+
return parameter.Value.Value;
7894
#endif
7995
}
8096
}
@@ -94,22 +110,25 @@ orderby entry.Quality descending
94110
{
95111
throw new ArgumentNullException( nameof( contentType ) );
96112
}
97-
98-
var comparer = StringComparer.OrdinalIgnoreCase;
99-
100-
foreach ( var parameter in contentType.Parameters )
101-
{
102113
#if WEBAPI
103-
if ( comparer.Equals( parameter.Name, ParameterName ) )
114+
var parameters = contentType.Parameters.ToArray();
115+
var count = parameters.Length;
116+
#else
117+
var parameters = contentType.Parameters;
118+
var count = parameters.Count;
119+
#endif
120+
for ( var i = 0; i < count; i++ )
121+
{
122+
var parameter = parameters[i];
123+
124+
if ( parameter.Name.Equals( ParameterName, OrdinalIgnoreCase ) )
104125
{
126+
#if WEBAPI
105127
return parameter.Value;
106-
}
107128
#else
108-
if ( comparer.Equals( parameter.Name.Value, ParameterName ) )
109-
{
110129
return parameter.Value.Value;
111-
}
112130
#endif
131+
}
113132
}
114133

115134
return null;
@@ -128,5 +147,8 @@ public virtual void AddParameters( IApiVersionParameterDescriptionContext contex
128147

129148
context.AddParameter( ParameterName, MediaTypeParameter );
130149
}
150+
151+
static int ByQualityDescending( MediaTypeWithQualityHeaderValue? left, MediaTypeWithQualityHeaderValue? right ) =>
152+
-Nullable.Compare( left?.Quality, right?.Quality );
131153
}
132154
}

src/Common/Versioning/QueryStringApiVersionReader.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,14 @@ public virtual void AddParameters( IApiVersionParameterDescriptionContext contex
7575
throw new ArgumentNullException( nameof( context ) );
7676
}
7777

78-
foreach ( var name in ParameterNames )
78+
var count = ParameterNames.Count;
79+
var names = new string[count];
80+
81+
ParameterNames.CopyTo( names, 0 );
82+
83+
for ( var i = 0; i < count; i++ )
7984
{
80-
context.AddParameter( name, Query );
85+
context.AddParameter( names[i], Query );
8186
}
8287
}
8388
}

src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/Description/VersionedApiExplorer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -949,7 +949,7 @@ static void AddUndeclaredRouteParameters( IParsedRoute parsedRoute, IDictionary<
949949
{
950950
for ( var i = 0; i < parsedRoute.PathSegments.Count; i++ )
951951
{
952-
if ( !( parsedRoute.PathSegments[i] is IPathContentSegment content ) )
952+
if ( parsedRoute.PathSegments[i] is not IPathContentSegment content )
953953
{
954954
continue;
955955
}
@@ -1055,7 +1055,7 @@ static bool MatchRegexConstraint( IHttpRoute route, string parameterName, string
10551055
}
10561056

10571057
// note that we don't support custom constraint (IHttpRouteConstraint) because it might rely on the request and some runtime states
1058-
if ( !( constraint is string constraintsRule ) )
1058+
if ( constraint is not string constraintsRule )
10591059
{
10601060
return true;
10611061
}

src/Microsoft.AspNet.WebApi.Versioning.ApiExplorer/System.Web.Http/Description/ApiDescriptionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public static bool TryUpdateRelativePathAndRemoveApiVersionParameter( this ApiDe
103103
throw new ArgumentNullException( nameof( options ) );
104104
}
105105

106-
if ( !options.SubstituteApiVersionInUrl || !( apiDescription is VersionedApiDescription versionedApiDescription ) )
106+
if ( !options.SubstituteApiVersionInUrl || apiDescription is not VersionedApiDescription versionedApiDescription )
107107
{
108108
return false;
109109
}

src/Microsoft.AspNet.WebApi.Versioning/System.Web.Http/HttpParameterBindingExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ static class HttpParameterBindingExtensions
99
{
1010
internal static bool WillReadUri( this HttpParameterBinding parameterBinding )
1111
{
12-
if ( !( parameterBinding is IValueProviderParameterBinding valueProviderParameterBinding ) )
12+
if ( parameterBinding is not IValueProviderParameterBinding valueProviderParameterBinding )
1313
{
1414
return false;
1515
}

0 commit comments

Comments
 (0)