From 5af05390067e7a7709262c47a0d9bd7ede3901ee Mon Sep 17 00:00:00 2001 From: campersau Date: Tue, 21 Apr 2026 08:41:22 +0200 Subject: [PATCH 1/2] Perf: avoid dictionary creation in QuerySting create the string directly use IEnumerable to avoid array creation in QueryStringListParameterAttribute --- src/Docker.DotNet/QueryString.cs | 35 +++++++++---------- .../QueryStringBoolParameterAttribute.cs | 2 +- .../QueryStringListParameterAttribute.cs | 4 +-- .../QueryStringMapParameterAttribute.cs | 2 +- .../QueryStringParameterAttribute.cs | 2 +- 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/Docker.DotNet/QueryString.cs b/src/Docker.DotNet/QueryString.cs index 74323ef9..55c5b017 100644 --- a/src/Docker.DotNet/QueryString.cs +++ b/src/Docker.DotNet/QueryString.cs @@ -21,9 +21,13 @@ public QueryString(T value) AttributedPublicProperties = FindAttributedPublicProperties(); } - public IDictionary GetKeyValuePairs() + /// + /// Returns formatted query string. + /// + /// + public string GetQueryString() { - var queryParameters = new Dictionary(); + var sb = new StringBuilder(); foreach (var pair in AttributedPublicProperties) { var property = pair.Key; @@ -41,26 +45,21 @@ public IDictionary GetKeyValuePairs() if (attribute.IsRequired || !IsDefaultOfType(value)) { var keyStr = attribute.Name; - var valueStr = attribute.Convert(value!); - queryParameters[keyStr] = valueStr; + foreach (var valueStr in attribute.Convert(value!)) + { + if (sb.Length > 0) + { + sb.Append('&'); + } + sb.Append(Uri.EscapeDataString(keyStr)); + sb.Append('='); + sb.Append(Uri.EscapeDataString(valueStr)); + } } } - return queryParameters; - } - - /// - /// Returns formatted query string. - /// - /// - public string GetQueryString() - { - return string.Join("&", - GetKeyValuePairs().Select( - pair => string.Join("&", - pair.Value.Select( - v => $"{Uri.EscapeDataString(pair.Key)}={Uri.EscapeDataString(v)}")))); + return sb.ToString(); } private static Dictionary FindAttributedPublicProperties< diff --git a/src/Docker.DotNet/QueryStringBoolParameterAttribute.cs b/src/Docker.DotNet/QueryStringBoolParameterAttribute.cs index 368b497f..836f030a 100644 --- a/src/Docker.DotNet/QueryStringBoolParameterAttribute.cs +++ b/src/Docker.DotNet/QueryStringBoolParameterAttribute.cs @@ -2,7 +2,7 @@ namespace Docker.DotNet; internal sealed class QueryStringBoolParameterAttribute(string name, bool required) : QueryStringParameterAttribute(name, required) { - public override string[] Convert(object value) + public override IEnumerable Convert(object value) { Debug.Assert(value != null); diff --git a/src/Docker.DotNet/QueryStringListParameterAttribute.cs b/src/Docker.DotNet/QueryStringListParameterAttribute.cs index 16e130f4..f71c133f 100644 --- a/src/Docker.DotNet/QueryStringListParameterAttribute.cs +++ b/src/Docker.DotNet/QueryStringListParameterAttribute.cs @@ -2,7 +2,7 @@ namespace Docker.DotNet; internal sealed class QueryStringListParameterAttribute(string name, bool required) : QueryStringParameterAttribute(name, required) { - public override string[] Convert(object value) + public override IEnumerable Convert(object value) { Debug.Assert(value != null); Debug.Assert(value is IList); @@ -12,6 +12,6 @@ public override string[] Convert(object value) throw new ArgumentException($"Expected value of type '{typeof(IList)}'.", nameof(value)); } - return typedValue.ToArray(); + return typedValue; } } \ No newline at end of file diff --git a/src/Docker.DotNet/QueryStringMapParameterAttribute.cs b/src/Docker.DotNet/QueryStringMapParameterAttribute.cs index c8fc3933..5fa1f4bb 100644 --- a/src/Docker.DotNet/QueryStringMapParameterAttribute.cs +++ b/src/Docker.DotNet/QueryStringMapParameterAttribute.cs @@ -2,7 +2,7 @@ namespace Docker.DotNet; internal sealed class QueryStringMapParameterAttribute(string name, bool required) : QueryStringParameterAttribute(name, required) { - public override string[] Convert(object value) + public override IEnumerable Convert(object value) { Debug.Assert(value != null); Debug.Assert(value is T); diff --git a/src/Docker.DotNet/QueryStringParameterAttribute.cs b/src/Docker.DotNet/QueryStringParameterAttribute.cs index 11032a6b..8090e2b3 100644 --- a/src/Docker.DotNet/QueryStringParameterAttribute.cs +++ b/src/Docker.DotNet/QueryStringParameterAttribute.cs @@ -7,7 +7,7 @@ internal class QueryStringParameterAttribute : Attribute public bool IsRequired { get; private set; } - public virtual string[] Convert(object value) => [value.ToString()!]; + public virtual IEnumerable Convert(object value) => [value.ToString()!]; public QueryStringParameterAttribute(string name, bool required) { From 1aa54f7e18f72bd3acd9a8a87508a4c3dd4d5fe0 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:39:05 +0200 Subject: [PATCH 2/2] chore: Use more meaningful names --- src/Docker.DotNet/QueryString.cs | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/Docker.DotNet/QueryString.cs b/src/Docker.DotNet/QueryString.cs index 55c5b017..735d5971 100644 --- a/src/Docker.DotNet/QueryString.cs +++ b/src/Docker.DotNet/QueryString.cs @@ -27,39 +27,42 @@ public QueryString(T value) /// public string GetQueryString() { - var sb = new StringBuilder(); - foreach (var pair in AttributedPublicProperties) + var queryStringBuilder = new StringBuilder(); + + foreach (var attributedProperty in AttributedPublicProperties) { - var property = pair.Key; - var attribute = pair.Value; + var property = attributedProperty.Key; + var attribute = attributedProperty.Value; + var value = property.GetValue(Object, null); // 'Required' check if (attribute.IsRequired && value == null) { - string propertyFullName = $"{property.DeclaringType?.FullName}.{property.Name}"; + var propertyFullName = $"{property.DeclaringType?.FullName}.{property.Name}"; throw new ArgumentException("Got null/unset value for a required query parameter.", propertyFullName); } - // Serialize + // Serialization if (attribute.IsRequired || !IsDefaultOfType(value)) { - var keyStr = attribute.Name; + var queryParameterName = attribute.Name; - foreach (var valueStr in attribute.Convert(value!)) + foreach (var queryParameterValue in attribute.Convert(value!)) { - if (sb.Length > 0) + if (queryStringBuilder.Length > 0) { - sb.Append('&'); + queryStringBuilder.Append('&'); } - sb.Append(Uri.EscapeDataString(keyStr)); - sb.Append('='); - sb.Append(Uri.EscapeDataString(valueStr)); + + queryStringBuilder.Append(Uri.EscapeDataString(queryParameterName)); + queryStringBuilder.Append('='); + queryStringBuilder.Append(Uri.EscapeDataString(queryParameterValue)); } } } - return sb.ToString(); + return queryStringBuilder.ToString(); } private static Dictionary FindAttributedPublicProperties<