Skip to content

Commit 03d7bbc

Browse files
feat: use TryGetSingle instead of collection enumerable to lists. (#1738)
Co-authored-by: Chris Pulman <[email protected]>
1 parent b320e4e commit 03d7bbc

File tree

3 files changed

+94
-71
lines changed

3 files changed

+94
-71
lines changed

Refit.Benchmarks/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99
{
1010
BenchmarkRunner.Run<EndToEndBenchmark>();
1111
// BenchmarkRunner.Run<StartupBenchmark>();
12-
// BenchmarkRunner.Run<PerformanceBenchmarks>();
12+
// BenchmarkRunner.Run<PerformanceBenchmark>();
1313
}

Refit/EnumerableExtensions.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace Refit;
2+
3+
internal static class EnumerableExtensions
4+
{
5+
internal static EnumerablePeek TryGetSingle<T>(this IEnumerable<T> enumerable, out T? value)
6+
{
7+
value = default(T);
8+
using var enumerator = enumerable.GetEnumerator();
9+
var hasFirst = enumerator.MoveNext();
10+
if (!hasFirst)
11+
return EnumerablePeek.Empty;
12+
13+
value = enumerator.Current;
14+
if (!enumerator.MoveNext())
15+
return EnumerablePeek.Single;
16+
17+
value = default(T);
18+
return EnumerablePeek.Many;
19+
}
20+
}
21+
22+
internal enum EnumerablePeek
23+
{
24+
Empty,
25+
Single,
26+
Many
27+
}

Refit/RestMethodInfo.cs

Lines changed: 66 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -85,29 +85,29 @@ public RestMethodInfoInternal(
8585
DetermineIfResponseMustBeDisposed();
8686

8787
// Exclude cancellation token parameters from this list
88-
var parameterList = methodInfo
88+
var parameterArray = methodInfo
8989
.GetParameters()
90-
.Where(p => p.ParameterType != typeof(CancellationToken))
91-
.ToList();
92-
ParameterInfoMap = parameterList
90+
.Where(static p => p.ParameterType != typeof(CancellationToken))
91+
.ToArray();
92+
ParameterInfoMap = parameterArray
9393
.Select((parameter, index) => new { index, parameter })
9494
.ToDictionary(x => x.index, x => x.parameter);
95-
ParameterMap = BuildParameterMap(RelativePath, parameterList);
96-
BodyParameterInfo = FindBodyParameter(parameterList, IsMultipart, hma.Method);
97-
AuthorizeParameterInfo = FindAuthorizationParameter(parameterList);
95+
ParameterMap = BuildParameterMap(RelativePath, parameterArray);
96+
BodyParameterInfo = FindBodyParameter(parameterArray, IsMultipart, hma.Method);
97+
AuthorizeParameterInfo = FindAuthorizationParameter(parameterArray);
9898

9999
Headers = ParseHeaders(methodInfo);
100-
HeaderParameterMap = BuildHeaderParameterMap(parameterList);
100+
HeaderParameterMap = BuildHeaderParameterMap(parameterArray);
101101
HeaderCollectionParameterMap = RestMethodInfoInternal.BuildHeaderCollectionParameterMap(
102-
parameterList
102+
parameterArray
103103
);
104-
PropertyParameterMap = BuildRequestPropertyMap(parameterList);
104+
PropertyParameterMap = BuildRequestPropertyMap(parameterArray);
105105

106106
// get names for multipart attachments
107107
AttachmentNameMap = [];
108108
if (IsMultipart)
109109
{
110-
for (var i = 0; i < parameterList.Count; i++)
110+
for (var i = 0; i < parameterArray.Length; i++)
111111
{
112112
if (
113113
ParameterMap.ContainsKey(i)
@@ -119,19 +119,19 @@ public RestMethodInfoInternal(
119119
continue;
120120
}
121121

122-
var attachmentName = GetAttachmentNameForParameter(parameterList[i]);
122+
var attachmentName = GetAttachmentNameForParameter(parameterArray[i]);
123123
if (attachmentName == null)
124124
continue;
125125

126126
AttachmentNameMap[i] = Tuple.Create(
127127
attachmentName,
128-
GetUrlNameForParameter(parameterList[i])
128+
GetUrlNameForParameter(parameterArray[i])
129129
);
130130
}
131131
}
132132

133133
QueryParameterMap = [];
134-
for (var i = 0; i < parameterList.Count; i++)
134+
for (var i = 0; i < parameterArray.Length; i++)
135135
{
136136
if (
137137
ParameterMap.ContainsKey(i)
@@ -145,21 +145,21 @@ public RestMethodInfoInternal(
145145
continue;
146146
}
147147

148-
QueryParameterMap.Add(i, GetUrlNameForParameter(parameterList[i]));
148+
QueryParameterMap.Add(i, GetUrlNameForParameter(parameterArray[i]));
149149
}
150150

151-
var ctParams = methodInfo
151+
var ctParamEnumerable = methodInfo
152152
.GetParameters()
153153
.Where(p => p.ParameterType == typeof(CancellationToken))
154-
.ToList();
155-
if (ctParams.Count > 1)
154+
.TryGetSingle(out var ctParam);
155+
if (ctParamEnumerable == EnumerablePeek.Many)
156156
{
157157
throw new ArgumentException(
158158
$"Argument list to method \"{methodInfo.Name}\" can only contain a single CancellationToken"
159159
);
160160
}
161161

162-
CancellationToken = ctParams.FirstOrDefault();
162+
CancellationToken = ctParam;
163163

164164
IsApiResponse =
165165
ReturnResultType!.GetTypeInfo().IsGenericType
@@ -170,31 +170,30 @@ public RestMethodInfoInternal(
170170
|| ReturnResultType == typeof(IApiResponse);
171171
}
172172

173-
static HashSet<int> BuildHeaderCollectionParameterMap(List<ParameterInfo> parameterList)
173+
static HashSet<int> BuildHeaderCollectionParameterMap(ParameterInfo[] parameterArray)
174174
{
175175
var headerCollectionMap = new HashSet<int>();
176176

177-
for (var i = 0; i < parameterList.Count; i++)
177+
for (var i = 0; i < parameterArray.Length; i++)
178178
{
179-
var param = parameterList[i];
179+
var param = parameterArray[i];
180180
var headerCollection = param
181181
.GetCustomAttributes(true)
182182
.OfType<HeaderCollectionAttribute>()
183183
.FirstOrDefault();
184184

185-
if (headerCollection != null)
185+
if (headerCollection == null) continue;
186+
187+
//opted for IDictionary<string, string> semantics here as opposed to the looser IEnumerable<KeyValuePair<string, string>> because IDictionary will enforce uniqueness of keys
188+
if (param.ParameterType.IsAssignableFrom(typeof(IDictionary<string, string>)))
186189
{
187-
//opted for IDictionary<string, string> semantics here as opposed to the looser IEnumerable<KeyValuePair<string, string>> because IDictionary will enforce uniqueness of keys
188-
if (param.ParameterType.IsAssignableFrom(typeof(IDictionary<string, string>)))
189-
{
190-
headerCollectionMap.Add(i);
191-
}
192-
else
193-
{
194-
throw new ArgumentException(
195-
$"HeaderCollection parameter of type {param.ParameterType.Name} is not assignable from IDictionary<string, string>"
196-
);
197-
}
190+
headerCollectionMap.Add(i);
191+
}
192+
else
193+
{
194+
throw new ArgumentException(
195+
$"HeaderCollection parameter of type {param.ParameterType.Name} is not assignable from IDictionary<string, string>"
196+
);
198197
}
199198
}
200199

@@ -209,13 +208,13 @@ static HashSet<int> BuildHeaderCollectionParameterMap(List<ParameterInfo> parame
209208
public RestMethodInfo ToRestMethodInfo() =>
210209
new(Name, Type, MethodInfo, RelativePath, ReturnType);
211210

212-
static Dictionary<int, string> BuildRequestPropertyMap(List<ParameterInfo> parameterList)
211+
static Dictionary<int, string> BuildRequestPropertyMap(ParameterInfo[] parameterArray)
213212
{
214213
var propertyMap = new Dictionary<int, string>();
215214

216-
for (var i = 0; i < parameterList.Count; i++)
215+
for (var i = 0; i < parameterArray.Length; i++)
217216
{
218-
var param = parameterList[i];
217+
var param = parameterArray[i];
219218
var propertyAttribute = param
220219
.GetCustomAttributes(true)
221220
.OfType<PropertyAttribute>()
@@ -233,12 +232,11 @@ static Dictionary<int, string> BuildRequestPropertyMap(List<ParameterInfo> param
233232
return propertyMap;
234233
}
235234

236-
static PropertyInfo[] GetParameterProperties(ParameterInfo parameter)
235+
static IEnumerable<PropertyInfo> GetParameterProperties(ParameterInfo parameter)
237236
{
238237
return parameter
239238
.ParameterType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
240-
.Where(p => p.CanRead && p.GetMethod?.IsPublic == true)
241-
.ToArray();
239+
.Where(static p => p.CanRead && p.GetMethod?.IsPublic == true);
242240
}
243241

244242
static void VerifyUrlPathIsSane(string relativePath)
@@ -254,7 +252,7 @@ static void VerifyUrlPathIsSane(string relativePath)
254252

255253
static Dictionary<int, RestMethodParameterInfo> BuildParameterMap(
256254
string relativePath,
257-
List<ParameterInfo> parameterInfo
255+
ParameterInfo[] parameterInfo
258256
)
259257
{
260258
var ret = new Dictionary<int, RestMethodParameterInfo>();
@@ -311,11 +309,11 @@ List<ParameterInfo> parameterInfo
311309
};
312310
#if NET6_0_OR_GREATER
313311
ret.TryAdd(
314-
parameterInfo.IndexOf(restMethodParameterInfo.ParameterInfo),
312+
Array.IndexOf(parameterInfo, restMethodParameterInfo.ParameterInfo),
315313
restMethodParameterInfo
316314
);
317315
#else
318-
var idx = parameterInfo.IndexOf(restMethodParameterInfo.ParameterInfo);
316+
var idx = Array.IndexOf(parameterInfo, restMethodParameterInfo.ParameterInfo);
319317
if (!ret.ContainsKey(idx))
320318
{
321319
ret.Add(idx, restMethodParameterInfo);
@@ -329,7 +327,7 @@ List<ParameterInfo> parameterInfo
329327
)
330328
{
331329
var property = value1;
332-
var parameterIndex = parameterInfo.IndexOf(property.Item1);
330+
var parameterIndex = Array.IndexOf(parameterInfo, property.Item1);
333331
//If we already have this parameter, add additional ParameterProperty
334332
if (ret.TryGetValue(parameterIndex, out var value2))
335333
{
@@ -355,12 +353,12 @@ List<ParameterInfo> parameterInfo
355353
);
356354
#if NET6_0_OR_GREATER
357355
ret.TryAdd(
358-
parameterInfo.IndexOf(restMethodParameterInfo.ParameterInfo),
356+
Array.IndexOf(parameterInfo, restMethodParameterInfo.ParameterInfo),
359357
restMethodParameterInfo
360358
);
361359
#else
362360
// Do the contains check
363-
var idx = parameterInfo.IndexOf(restMethodParameterInfo.ParameterInfo);
361+
var idx = Array.IndexOf(parameterInfo, restMethodParameterInfo.ParameterInfo);
364362
if (!ret.ContainsKey(idx))
365363
{
366364
ret.Add(idx, restMethodParameterInfo);
@@ -421,7 +419,7 @@ HttpMethod method
421419
// 2) POST/PUT/PATCH: Reference type other than string
422420
// 3) If there are two reference types other than string, without the body attribute, throw
423421

424-
var bodyParams = parameterList
422+
var bodyParamEnumerable = parameterList
425423
.Select(
426424
x =>
427425
new
@@ -433,12 +431,12 @@ HttpMethod method
433431
}
434432
)
435433
.Where(x => x.BodyAttribute != null)
436-
.ToList();
434+
.TryGetSingle(out var bodyParam);
437435

438436
// multipart requests may not contain a body, implicit or explicit
439437
if (isMultipart)
440438
{
441-
if (bodyParams.Count > 0)
439+
if (bodyParamEnumerable != EnumerablePeek.Empty)
442440
{
443441
throw new ArgumentException(
444442
"Multipart requests may not contain a Body parameter"
@@ -447,19 +445,18 @@ HttpMethod method
447445
return null;
448446
}
449447

450-
if (bodyParams.Count > 1)
448+
if (bodyParamEnumerable == EnumerablePeek.Many)
451449
{
452450
throw new ArgumentException("Only one parameter can be a Body parameter");
453451
}
454452

455453
// #1, body attribute wins
456-
if (bodyParams.Count == 1)
454+
if (bodyParamEnumerable == EnumerablePeek.Single)
457455
{
458-
var ret = bodyParams[0];
459456
return Tuple.Create(
460-
ret.BodyAttribute!.SerializationMethod,
461-
ret.BodyAttribute.Buffered ?? RefitSettings.Buffered,
462-
parameterList.IndexOf(ret.Parameter)
457+
bodyParam!.BodyAttribute!.SerializationMethod,
458+
bodyParam.BodyAttribute.Buffered ?? RefitSettings.Buffered,
459+
parameterList.IndexOf(bodyParam.Parameter)
463460
);
464461
}
465462

@@ -475,7 +472,7 @@ HttpMethod method
475472

476473
// see if we're a post/put/patch
477474
// explicitly skip [Query], [HeaderCollection], and [Property]-denoted params
478-
var refParams = parameterList
475+
var refParamEnumerable = parameterList
479476
.Where(
480477
pi =>
481478
!pi.ParameterType.GetTypeInfo().IsValueType
@@ -484,22 +481,22 @@ HttpMethod method
484481
&& pi.GetCustomAttribute<HeaderCollectionAttribute>() == null
485482
&& pi.GetCustomAttribute<PropertyAttribute>() == null
486483
)
487-
.ToList();
484+
.TryGetSingle(out var refParam);
488485

489486
// Check for rule #3
490-
if (refParams.Count > 1)
487+
if (refParamEnumerable == EnumerablePeek.Many)
491488
{
492489
throw new ArgumentException(
493490
"Multiple complex types found. Specify one parameter as the body using BodyAttribute"
494491
);
495492
}
496493

497-
if (refParams.Count == 1)
494+
if (refParamEnumerable == EnumerablePeek.Single)
498495
{
499496
return Tuple.Create(
500497
BodySerializationMethod.Serialized,
501498
RefitSettings.Buffered,
502-
parameterList.IndexOf(refParams[0])
499+
parameterList.IndexOf(refParam!)
503500
);
504501
}
505502

@@ -508,7 +505,7 @@ HttpMethod method
508505

509506
static Tuple<string, int>? FindAuthorizationParameter(IList<ParameterInfo> parameterList)
510507
{
511-
var authorizeParams = parameterList
508+
var authorizeParamsEnumerable = parameterList
512509
.Select(
513510
x =>
514511
new
@@ -520,19 +517,18 @@ HttpMethod method
520517
}
521518
)
522519
.Where(x => x.AuthorizeAttribute != null)
523-
.ToList();
520+
.TryGetSingle(out var authorizeParam);
524521

525-
if (authorizeParams.Count > 1)
522+
if (authorizeParamsEnumerable == EnumerablePeek.Many)
526523
{
527524
throw new ArgumentException("Only one parameter can be an Authorize parameter");
528525
}
529526

530-
if (authorizeParams.Count == 1)
527+
if (authorizeParamsEnumerable == EnumerablePeek.Single)
531528
{
532-
var ret = authorizeParams[0];
533529
return Tuple.Create(
534-
ret.AuthorizeAttribute!.Scheme,
535-
parameterList.IndexOf(ret.Parameter)
530+
authorizeParam!.AuthorizeAttribute!.Scheme,
531+
parameterList.IndexOf(authorizeParam.Parameter)
536532
);
537533
}
538534

@@ -582,13 +578,13 @@ HttpMethod method
582578
return ret;
583579
}
584580

585-
static Dictionary<int, string> BuildHeaderParameterMap(List<ParameterInfo> parameterList)
581+
static Dictionary<int, string> BuildHeaderParameterMap(ParameterInfo[] parameterArray)
586582
{
587583
var ret = new Dictionary<int, string>();
588584

589-
for (var i = 0; i < parameterList.Count; i++)
585+
for (var i = 0; i < parameterArray.Length; i++)
590586
{
591-
var header = parameterList[i]
587+
var header = parameterArray[i]
592588
.GetCustomAttributes(true)
593589
.OfType<HeaderAttribute>()
594590
.Select(ha => ha.Header)

0 commit comments

Comments
 (0)