From 1b5fa017c2218b0f30fd60dd2ff74bb3bb23ee9b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 8 Mar 2026 23:10:11 +0000
Subject: [PATCH 1/6] Initial plan
From d056b5cc71d36b7005308621c43f3a7bc5af5086 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 8 Mar 2026 23:16:07 +0000
Subject: [PATCH 2/6] Make reflection cache opt-in via ReflectionCacheSettings
Co-authored-by: baal2000 <22180333+baal2000@users.noreply.github.com>
---
.../Google.Apis.Core/Util/ReflectionCache.cs | 32 ++++---
.../Util/ReflectionCacheSettings.cs | 42 ++++++++++
.../Requests/Parameters/ParameterUtilsTest.cs | 84 +++++++++++++++----
.../Apis/Utils/ReflectionCacheTest.cs | 63 +++++++++++++-
4 files changed, 191 insertions(+), 30 deletions(-)
create mode 100644 Src/Support/Google.Apis.Core/Util/ReflectionCacheSettings.cs
diff --git a/Src/Support/Google.Apis.Core/Util/ReflectionCache.cs b/Src/Support/Google.Apis.Core/Util/ReflectionCache.cs
index 2f5d87831ac..83747ea1f34 100644
--- a/Src/Support/Google.Apis.Core/Util/ReflectionCache.cs
+++ b/Src/Support/Google.Apis.Core/Util/ReflectionCache.cs
@@ -25,8 +25,8 @@ namespace Google.Apis.Util
/// Provides cached reflection results for request parameter discovery.
///
///
- /// This cache is intentionally unbounded and keyed by request type. The set of request types decorated with
- /// is expected to be finite and stable for the lifetime of the application.
+ /// This cache is only used when is set to true.
+ /// By default, reflection results are recomputed on each call to avoid memory overhead.
///
internal static partial class ReflectionCache
{
@@ -43,20 +43,32 @@ internal static partial class ReflectionCache
new ConcurrentDictionary();
///
- /// Returns the cached set of request-parameter properties for the specified request type.
+ /// Returns the set of request-parameter properties for the specified request type.
+ /// Uses caching if is enabled.
///
/// The type to get request parameter properties for.
/// An array of structs containing properties and their RequestParameterAttribute.
internal static PropertyWithAttribute[] GetRequestParameterProperties(Type type)
{
- return RequestParameterPropertiesCache.GetOrAdd(type, t =>
+ // Only use cache if explicitly enabled by user
+ if (ReflectionCacheSettings.EnableReflectionCache)
{
- // Get properties, filter by attribute, and cache only the filtered result
- return t.GetProperties(BindingFlags.Instance | BindingFlags.Public)
- .Select(prop => new PropertyWithAttribute(prop, prop.GetCustomAttribute(inherit: false)))
- .Where(pwa => pwa.Attribute != null)
- .ToArray();
- });
+ return RequestParameterPropertiesCache.GetOrAdd(type, ComputeProperties);
+ }
+
+ // Default behavior: compute properties without caching
+ return ComputeProperties(type);
+ }
+
+ ///
+ /// Computes the request parameter properties for a given type using reflection.
+ ///
+ private static PropertyWithAttribute[] ComputeProperties(Type type)
+ {
+ return type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
+ .Select(prop => new PropertyWithAttribute(prop, prop.GetCustomAttribute(inherit: false)))
+ .Where(pwa => pwa.Attribute != null)
+ .ToArray();
}
}
}
diff --git a/Src/Support/Google.Apis.Core/Util/ReflectionCacheSettings.cs b/Src/Support/Google.Apis.Core/Util/ReflectionCacheSettings.cs
new file mode 100644
index 00000000000..246ca0369c4
--- /dev/null
+++ b/Src/Support/Google.Apis.Core/Util/ReflectionCacheSettings.cs
@@ -0,0 +1,42 @@
+/*
+Copyright 2026 Google Inc
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+namespace Google.Apis.Util
+{
+ ///
+ /// Configuration settings for reflection caching behavior.
+ ///
+ public static class ReflectionCacheSettings
+ {
+ ///
+ /// Gets or sets whether to enable reflection result caching for request parameters.
+ ///
+ ///
+ ///
+ /// When enabled, PropertyInfo objects for request parameter properties are cached,
+ /// eliminating repeated reflection overhead for the same request types.
+ ///
+ ///
+ /// This should be set once at application startup before making any API requests.
+ ///
+ ///
+ /// Default is false to preserve existing behavior. Enable this if you are making
+ /// many requests with the same request types and reflection overhead is a bottleneck.
+ ///
+ ///
+ public static bool EnableReflectionCache { get; set; }
+ }
+}
diff --git a/Src/Support/Google.Apis.Tests/Apis/Requests/Parameters/ParameterUtilsTest.cs b/Src/Support/Google.Apis.Tests/Apis/Requests/Parameters/ParameterUtilsTest.cs
index 97e8903ec8b..3327b08eec0 100644
--- a/Src/Support/Google.Apis.Tests/Apis/Requests/Parameters/ParameterUtilsTest.cs
+++ b/Src/Support/Google.Apis.Tests/Apis/Requests/Parameters/ParameterUtilsTest.cs
@@ -261,25 +261,36 @@ public void InitParametersWithExpansion_BooleanConversion()
[Fact]
public void InitParametersWithExpansion_CacheUsage()
{
- var request1 = new TestRequestWithScalars { Name = "test1", Id = 1 };
- var request2 = new TestRequestWithScalars { Name = "test2", Id = 2 };
- var builder1 = new RequestBuilder { BaseUri = new Uri("https://example.com/api") };
- var builder2 = new RequestBuilder { BaseUri = new Uri("https://example.com/api") };
-
- // First call - cache is populated
- ParameterUtils.InitParametersWithExpansion(builder1, request1);
- var properties1 = Google.Apis.Util.ReflectionCache.GetRequestParameterProperties(typeof(TestRequestWithScalars));
-
- // Second call - should reuse cached PropertyInfo
- ParameterUtils.InitParametersWithExpansion(builder2, request2);
- var properties2 = Google.Apis.Util.ReflectionCache.GetRequestParameterProperties(typeof(TestRequestWithScalars));
-
- // Verify same PropertyInfo instances are returned (object reference equality)
- Assert.Equal(properties1.Length, properties2.Length);
- for (int i = 0; i < properties1.Length; i++)
+ var originalState = ReflectionCacheSettings.EnableReflectionCache;
+ try
{
- Assert.Same(properties1[i].Property, properties2[i].Property);
- Assert.Same(properties1[i].Attribute, properties2[i].Attribute);
+ // Arrange - explicitly enable cache
+ ReflectionCacheSettings.EnableReflectionCache = true;
+
+ var request1 = new TestRequestWithScalars { Name = "test1", Id = 1 };
+ var request2 = new TestRequestWithScalars { Name = "test2", Id = 2 };
+ var builder1 = new RequestBuilder { BaseUri = new Uri("https://example.com/api") };
+ var builder2 = new RequestBuilder { BaseUri = new Uri("https://example.com/api") };
+
+ // First call - cache is populated
+ ParameterUtils.InitParametersWithExpansion(builder1, request1);
+ var properties1 = Google.Apis.Util.ReflectionCache.GetRequestParameterProperties(typeof(TestRequestWithScalars));
+
+ // Second call - should reuse cached PropertyInfo
+ ParameterUtils.InitParametersWithExpansion(builder2, request2);
+ var properties2 = Google.Apis.Util.ReflectionCache.GetRequestParameterProperties(typeof(TestRequestWithScalars));
+
+ // Verify same PropertyInfo instances are returned (object reference equality)
+ Assert.Equal(properties1.Length, properties2.Length);
+ for (int i = 0; i < properties1.Length; i++)
+ {
+ Assert.Same(properties1[i].Property, properties2[i].Property);
+ Assert.Same(properties1[i].Attribute, properties2[i].Attribute);
+ }
+ }
+ finally
+ {
+ ReflectionCacheSettings.EnableReflectionCache = originalState;
}
}
@@ -338,5 +349,42 @@ public void InitParametersWithExpansion_NullElementsInEnumerable()
Assert.Contains($"part={Uri.EscapeDataString(expectedNullValue)}", query);
}
}
+
+ [Theory]
+ [InlineData(false)] // Cache disabled (default)
+ [InlineData(true)] // Cache enabled
+ public void IterateParameters_WorksWithBothCacheModes(bool enableCache)
+ {
+ // Arrange
+ var originalState = ReflectionCacheSettings.EnableReflectionCache;
+ try
+ {
+ ReflectionCacheSettings.EnableReflectionCache = enableCache;
+ var request = new TestRequestUrl()
+ {
+ FirstParam = "firstOne",
+ SecondParam = "secondOne",
+ ParamsCollection = new List>{
+ new KeyValuePair("customParam1","customVal1"),
+ new KeyValuePair("customParam2","customVal2")
+ }
+ };
+
+ // Act
+ var result = request.Build().AbsoluteUri;
+
+ // Assert - behavior should be identical regardless of cache setting
+ Assert.Contains("first_query_param=firstOne", result);
+ Assert.Contains("second_query_param=secondOne", result);
+ Assert.Contains("customParam1=customVal1", result);
+ Assert.Contains("customParam2=customVal2", result);
+ Assert.DoesNotContain("query_param_attribute_name", result);
+ }
+ finally
+ {
+ // Restore original state
+ ReflectionCacheSettings.EnableReflectionCache = originalState;
+ }
+ }
}
}
diff --git a/Src/Support/Google.Apis.Tests/Apis/Utils/ReflectionCacheTest.cs b/Src/Support/Google.Apis.Tests/Apis/Utils/ReflectionCacheTest.cs
index 32bf4ae2012..013c2bd1152 100644
--- a/Src/Support/Google.Apis.Tests/Apis/Utils/ReflectionCacheTest.cs
+++ b/Src/Support/Google.Apis.Tests/Apis/Utils/ReflectionCacheTest.cs
@@ -15,14 +15,28 @@ limitations under the License.
*/
using Google.Apis.Util;
+using System;
using System.Linq;
using Xunit;
namespace Google.Apis.Tests.Apis.Utils
{
/// Tests for .
- public class ReflectionCacheTest
+ public class ReflectionCacheTest : IDisposable
{
+ private readonly bool _originalCacheState;
+
+ public ReflectionCacheTest()
+ {
+ // Save original state to restore after each test
+ _originalCacheState = ReflectionCacheSettings.EnableReflectionCache;
+ }
+
+ public void Dispose()
+ {
+ // Restore original state after each test
+ ReflectionCacheSettings.EnableReflectionCache = _originalCacheState;
+ }
private class TestClass
{
[RequestParameter("test_param", RequestParameterType.Query)]
@@ -37,6 +51,9 @@ private class TestClass
[Fact]
public void GetRequestParameterPropertiesWithAttribute_ReturnsPropertiesAndAttributes()
{
+ // Arrange - cache disabled (default behavior)
+ ReflectionCacheSettings.EnableReflectionCache = false;
+
// Act
var propertiesWithAttributes = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));
@@ -64,6 +81,9 @@ public void GetRequestParameterPropertiesWithAttribute_ReturnsPropertiesAndAttri
[Fact]
public void GetRequestParameterPropertiesWithAttribute_CachesResults()
{
+ // Arrange - explicitly enable cache
+ ReflectionCacheSettings.EnableReflectionCache = true;
+
// Act - Call twice
var result1 = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));
var result2 = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));
@@ -82,6 +102,9 @@ public void GetRequestParameterPropertiesWithAttribute_CachesResults()
[Fact]
public void GetRequestParameterProperties_ReturnsOnlyPropertiesWithAttribute()
{
+ // Arrange - cache disabled (default behavior)
+ ReflectionCacheSettings.EnableReflectionCache = false;
+
// Act
var properties = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));
// Assert
@@ -95,8 +118,11 @@ public void GetRequestParameterProperties_ReturnsOnlyPropertiesWithAttribute()
[Fact]
public void GetRequestParameterPropertiesWithAttribute_RegressionTest_NoNewInstancesCreated()
{
- // This regression test ensures that repeated calls don't create new PropertyInfo or Attribute instances.
+ // This regression test ensures that repeated calls with cache enabled don't create new PropertyInfo or Attribute instances.
+ // Arrange - explicitly enable cache
+ ReflectionCacheSettings.EnableReflectionCache = true;
+
// Act - Get properties multiple times
var result1 = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));
var result2 = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));
@@ -115,5 +141,38 @@ public void GetRequestParameterPropertiesWithAttribute_RegressionTest_NoNewInsta
Assert.Same(result2[i].Attribute, result3[i].Attribute);
}
}
+
+ [Fact]
+ public void GetRequestParameterProperties_WithCacheDisabled_ReturnsDifferentInstanceOnSecondCall()
+ {
+ // Arrange
+ ReflectionCacheSettings.EnableReflectionCache = false;
+
+ // Act
+ var firstCall = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));
+ var secondCall = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));
+
+ // Assert - should be different array instances when not caching
+ Assert.NotSame(firstCall, secondCall);
+
+ // But content should be equivalent
+ Assert.Equal(firstCall.Length, secondCall.Length);
+ }
+
+ [Fact]
+ public void GetRequestParameterProperties_DefaultBehaviorIsNotCached()
+ {
+ // Arrange - don't set EnableReflectionCache, use default (false)
+ // (IDisposable restores the original state, so this tests a fresh-default scenario
+ // only when run as the first test; checking NotSame is sufficient)
+ ReflectionCacheSettings.EnableReflectionCache = false;
+
+ // Act
+ var firstCall = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));
+ var secondCall = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));
+
+ // Assert - default should be no caching
+ Assert.NotSame(firstCall, secondCall);
+ }
}
}
From 6010121068fa569d7954a28960d713da7d7c94dc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 9 Mar 2026 01:39:05 +0000
Subject: [PATCH 3/6] Make ReflectionCache and PropertyWithAttribute public
with comprehensive XML docs
Co-authored-by: baal2000 <22180333+baal2000@users.noreply.github.com>
---
.../Util/PropertyWithAttribute.cs | 25 ++++++---
.../Google.Apis.Core/Util/ReflectionCache.cs | 53 ++++++++++++++++---
2 files changed, 63 insertions(+), 15 deletions(-)
diff --git a/Src/Support/Google.Apis.Core/Util/PropertyWithAttribute.cs b/Src/Support/Google.Apis.Core/Util/PropertyWithAttribute.cs
index 3602a483a21..54523facfcc 100644
--- a/Src/Support/Google.Apis.Core/Util/PropertyWithAttribute.cs
+++ b/Src/Support/Google.Apis.Core/Util/PropertyWithAttribute.cs
@@ -19,25 +19,36 @@ limitations under the License.
namespace Google.Apis.Util
{
///
- /// Represents a property with its associated RequestParameterAttribute.
+ /// Pairs a with its associated .
///
- internal readonly struct PropertyWithAttribute
+ ///
+ /// Instances of this struct are produced by
+ /// and consumed by ParameterUtils when building request URLs and form bodies. Only properties
+ /// that are decorated with are represented; properties without
+ /// the attribute are filtered out before any value is created.
+ ///
+ public readonly struct PropertyWithAttribute
{
///
- /// The PropertyInfo for the property.
+ /// Gets the for the request parameter property.
///
public PropertyInfo Property { get; }
///
- /// The RequestParameterAttribute associated with this property.
+ /// Gets the applied to .
///
+ ///
+ /// This value is never null on instances returned by
+ /// ; properties without the attribute
+ /// are excluded from the results.
+ ///
public RequestParameterAttribute Attribute { get; }
///
- /// Initializes a new instance of PropertyWithAttribute.
+ /// Initializes a new instance of .
///
- /// The property info.
- /// The associated .
+ /// The of the request parameter property.
+ /// The applied to .
public PropertyWithAttribute(PropertyInfo property, RequestParameterAttribute attribute)
{
Property = property;
diff --git a/Src/Support/Google.Apis.Core/Util/ReflectionCache.cs b/Src/Support/Google.Apis.Core/Util/ReflectionCache.cs
index 83747ea1f34..a0eb234eeeb 100644
--- a/Src/Support/Google.Apis.Core/Util/ReflectionCache.cs
+++ b/Src/Support/Google.Apis.Core/Util/ReflectionCache.cs
@@ -25,10 +25,37 @@ namespace Google.Apis.Util
/// Provides cached reflection results for request parameter discovery.
///
///
- /// This cache is only used when is set to true.
- /// By default, reflection results are recomputed on each call to avoid memory overhead.
+ ///
+ /// This class is thread-safe. The internal cache uses ,
+ /// which allows concurrent reads and writes without external locking.
+ ///
+ ///
+ /// Caching is opt-in: set to true
+ /// at application startup to activate it. By default, reflection results are recomputed on every call
+ /// to preserve the existing no-overhead-at-rest behavior.
+ ///
+ ///
+ /// When caching is enabled, each unique request type incurs a one-time reflection cost. Subsequent calls
+ /// for the same type return the cached array directly, eliminating
+ /// per-call reflection and attribute-lookup overhead.
+ ///
+ ///
+ /// The cache is intentionally unbounded, but in practice it is finite: entries are keyed by the concrete
+ /// request types that carry -decorated properties. The set of such
+ /// types in any application is small and fixed at compile time.
+ ///
///
- internal static partial class ReflectionCache
+ ///
+ /// Enable caching once at application startup, before issuing any API requests:
+ ///
+ /// // Enable caching at application startup
+ /// ReflectionCacheSettings.EnableReflectionCache = true;
+ ///
+ /// // The cache is used automatically by ParameterUtils
+ /// // (no further configuration required)
+ ///
+ ///
+ public static partial class ReflectionCache
{
///
/// Cache of properties filtered by RequestParameterAttribute.
@@ -43,12 +70,22 @@ internal static partial class ReflectionCache
new ConcurrentDictionary();
///
- /// Returns the set of request-parameter properties for the specified request type.
- /// Uses caching if is enabled.
+ /// Returns the set of -decorated properties for the specified
+ /// request type.
///
- /// The type to get request parameter properties for.
- /// An array of structs containing properties and their RequestParameterAttribute.
- internal static PropertyWithAttribute[] GetRequestParameterProperties(Type type)
+ /// The request type whose parameter properties should be returned.
+ ///
+ /// An array of values, each pairing a
+ /// with its .
+ /// Only properties that carry the attribute are included; properties without it are omitted.
+ ///
+ ///
+ /// When is true, the result is
+ /// stored in an internal and returned on subsequent
+ /// calls without re-executing reflection. When the setting is false (the default), reflection
+ /// is performed on every invocation.
+ ///
+ public static PropertyWithAttribute[] GetRequestParameterProperties(Type type)
{
// Only use cache if explicitly enabled by user
if (ReflectionCacheSettings.EnableReflectionCache)
From 31444f7b8049a95a125960c1d20870110e8bfa0d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 9 Mar 2026 01:44:39 +0000
Subject: [PATCH 4/6] Add value documentation tags to public properties in
PropertyWithAttribute
Co-authored-by: baal2000 <22180333+baal2000@users.noreply.github.com>
---
Src/Support/Google.Apis.Core/Util/PropertyWithAttribute.cs | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/Src/Support/Google.Apis.Core/Util/PropertyWithAttribute.cs b/Src/Support/Google.Apis.Core/Util/PropertyWithAttribute.cs
index 54523facfcc..5bdfb7bd346 100644
--- a/Src/Support/Google.Apis.Core/Util/PropertyWithAttribute.cs
+++ b/Src/Support/Google.Apis.Core/Util/PropertyWithAttribute.cs
@@ -32,11 +32,18 @@ public readonly struct PropertyWithAttribute
///
/// Gets the for the request parameter property.
///
+ ///
+ /// The that describes the request parameter property on the request type.
+ ///
public PropertyInfo Property { get; }
///
/// Gets the applied to .
///
+ ///
+ /// The that annotates , providing the
+ /// parameter name and used when serializing the request.
+ ///
///
/// This value is never null on instances returned by
/// ; properties without the attribute
From 953f715b916d5d7eb2937ba110ee844ccad39368 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 9 Mar 2026 01:53:51 +0000
Subject: [PATCH 5/6] Remove InternalsVisibleTo Google.Apis; promote
InitParametersWithExpansion to public
Co-authored-by: baal2000 <22180333+baal2000@users.noreply.github.com>
---
Src/Support/Google.Apis.Core/AssemblyInfo.cs | 1 -
.../Google.Apis.Core/Requests/Parameters/ParameterUtils.cs | 5 +----
2 files changed, 1 insertion(+), 5 deletions(-)
diff --git a/Src/Support/Google.Apis.Core/AssemblyInfo.cs b/Src/Support/Google.Apis.Core/AssemblyInfo.cs
index 747aa2a2c86..9d929c12b56 100644
--- a/Src/Support/Google.Apis.Core/AssemblyInfo.cs
+++ b/Src/Support/Google.Apis.Core/AssemblyInfo.cs
@@ -16,6 +16,5 @@ limitations under the License.
using System.Runtime.CompilerServices;
-[assembly: InternalsVisibleTo("Google.Apis,PublicKey=00240000048000009400000006020000002400005253413100040000010001003d69fa08add2ea7341cc102edb2f3a59bb49e7f7c8bf1bd96d494013c194f4d80ee30278f20e08a0b7cb863d6522d8c1c0071dd36748297deefeb99e899e6a80b9ddc490e88ea566d2f7d4f442211f7beb6b2387fb435bfaa3ecfe7afc0184cc46f80a5866e6bb8eb73f64a3964ed82f6a5036b91b1ac93e1f44508b65e51fce")]
[assembly: InternalsVisibleTo("Google.Apis.Tests,PublicKey=00240000048000009400000006020000002400005253413100040000010001003d69fa08add2ea7341cc102edb2f3a59bb49e7f7c8bf1bd96d494013c194f4d80ee30278f20e08a0b7cb863d6522d8c1c0071dd36748297deefeb99e899e6a80b9ddc490e88ea566d2f7d4f442211f7beb6b2387fb435bfaa3ecfe7afc0184cc46f80a5866e6bb8eb73f64a3964ed82f6a5036b91b1ac93e1f44508b65e51fce")]
[assembly: InternalsVisibleTo("Google.Apis.IntegrationTests,PublicKey=00240000048000009400000006020000002400005253413100040000010001003d69fa08add2ea7341cc102edb2f3a59bb49e7f7c8bf1bd96d494013c194f4d80ee30278f20e08a0b7cb863d6522d8c1c0071dd36748297deefeb99e899e6a80b9ddc490e88ea566d2f7d4f442211f7beb6b2387fb435bfaa3ecfe7afc0184cc46f80a5866e6bb8eb73f64a3964ed82f6a5036b91b1ac93e1f44508b65e51fce")]
diff --git a/Src/Support/Google.Apis.Core/Requests/Parameters/ParameterUtils.cs b/Src/Support/Google.Apis.Core/Requests/Parameters/ParameterUtils.cs
index 979b78deb8a..2c1d58ec3b5 100644
--- a/Src/Support/Google.Apis.Core/Requests/Parameters/ParameterUtils.cs
+++ b/Src/Support/Google.Apis.Core/Requests/Parameters/ParameterUtils.cs
@@ -155,10 +155,7 @@ public static void InitParameters(RequestBuilder builder, object request)
/// attribute. Those properties will be set in the
/// given request builder object
///
- ///
- /// This method is internal and is called from the Google.Apis assembly via InternalsVisibleTo.
- ///
- internal static void InitParametersWithExpansion(RequestBuilder builder, object request)
+ public static void InitParametersWithExpansion(RequestBuilder builder, object request)
{
// Use typed methods to preserve RequestParameterType information
var parametersWithTypes = CreateParameterDictionaryWithTypes(request);
From 0ea6b0e6f46cacf35bd2f9362655287876193c0b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 9 Mar 2026 08:05:36 +0000
Subject: [PATCH 6/6] Consolidate EnableReflectionCache into
ApplicationContext; remove ReflectionCacheSettings
Co-authored-by: baal2000 <22180333+baal2000@users.noreply.github.com>
---
.../Google.Apis.Core/ApplicationContext.cs | 24 ++++++++++-
.../Google.Apis.Core/Util/ReflectionCache.cs | 9 ++--
.../Util/ReflectionCacheSettings.cs | 42 -------------------
.../Requests/Parameters/ParameterUtilsTest.cs | 13 +++---
.../Apis/Utils/ReflectionCacheTest.cs | 17 ++++----
5 files changed, 43 insertions(+), 62 deletions(-)
delete mode 100644 Src/Support/Google.Apis.Core/Util/ReflectionCacheSettings.cs
diff --git a/Src/Support/Google.Apis.Core/ApplicationContext.cs b/Src/Support/Google.Apis.Core/ApplicationContext.cs
index d316446bae7..e9207ac105e 100644
--- a/Src/Support/Google.Apis.Core/ApplicationContext.cs
+++ b/Src/Support/Google.Apis.Core/ApplicationContext.cs
@@ -19,13 +19,33 @@ limitations under the License.
namespace Google
{
- /// Defines the context in which this library runs. It allows setting up custom loggers.
+ /// Defines the context in which this library runs. It allows setting up custom loggers and performance options.
public static class ApplicationContext
{
private static ILogger logger;
// For testing
- internal static void Reset() => logger = null;
+ internal static void Reset()
+ {
+ logger = null;
+ EnableReflectionCache = false;
+ }
+
+ ///
+ /// Gets or sets whether to enable reflection result caching for request parameter properties.
+ ///
+ ///
+ ///
+ /// When enabled, lookups for request parameter
+ /// properties are cached per request type, eliminating repeated reflection overhead.
+ ///
+ ///
+ /// Default is false. Set to true early in application startup before making
+ /// any API requests. This setting is intended for applications that make many requests and
+ /// where reflection overhead has been identified as a bottleneck.
+ ///
+ ///
+ public static bool EnableReflectionCache { get; set; }
/// Returns the logger used within this application context.
/// It creates a if no logger was registered previously
diff --git a/Src/Support/Google.Apis.Core/Util/ReflectionCache.cs b/Src/Support/Google.Apis.Core/Util/ReflectionCache.cs
index a0eb234eeeb..8951fe40586 100644
--- a/Src/Support/Google.Apis.Core/Util/ReflectionCache.cs
+++ b/Src/Support/Google.Apis.Core/Util/ReflectionCache.cs
@@ -18,6 +18,7 @@ limitations under the License.
using System.Collections.Concurrent;
using System.Linq;
using System.Reflection;
+using Google;
namespace Google.Apis.Util
{
@@ -30,7 +31,7 @@ namespace Google.Apis.Util
/// which allows concurrent reads and writes without external locking.
///
///
- /// Caching is opt-in: set to true
+ /// Caching is opt-in: set to true
/// at application startup to activate it. By default, reflection results are recomputed on every call
/// to preserve the existing no-overhead-at-rest behavior.
///
@@ -49,7 +50,7 @@ namespace Google.Apis.Util
/// Enable caching once at application startup, before issuing any API requests:
///
/// // Enable caching at application startup
- /// ReflectionCacheSettings.EnableReflectionCache = true;
+ /// ApplicationContext.EnableReflectionCache = true;
///
/// // The cache is used automatically by ParameterUtils
/// // (no further configuration required)
@@ -80,7 +81,7 @@ public static partial class ReflectionCache
/// Only properties that carry the attribute are included; properties without it are omitted.
///
///
- /// When is true, the result is
+ /// When is true, the result is
/// stored in an internal and returned on subsequent
/// calls without re-executing reflection. When the setting is false (the default), reflection
/// is performed on every invocation.
@@ -88,7 +89,7 @@ public static partial class ReflectionCache
public static PropertyWithAttribute[] GetRequestParameterProperties(Type type)
{
// Only use cache if explicitly enabled by user
- if (ReflectionCacheSettings.EnableReflectionCache)
+ if (ApplicationContext.EnableReflectionCache)
{
return RequestParameterPropertiesCache.GetOrAdd(type, ComputeProperties);
}
diff --git a/Src/Support/Google.Apis.Core/Util/ReflectionCacheSettings.cs b/Src/Support/Google.Apis.Core/Util/ReflectionCacheSettings.cs
deleted file mode 100644
index 246ca0369c4..00000000000
--- a/Src/Support/Google.Apis.Core/Util/ReflectionCacheSettings.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
-Copyright 2026 Google Inc
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-namespace Google.Apis.Util
-{
- ///
- /// Configuration settings for reflection caching behavior.
- ///
- public static class ReflectionCacheSettings
- {
- ///
- /// Gets or sets whether to enable reflection result caching for request parameters.
- ///
- ///
- ///
- /// When enabled, PropertyInfo objects for request parameter properties are cached,
- /// eliminating repeated reflection overhead for the same request types.
- ///
- ///
- /// This should be set once at application startup before making any API requests.
- ///
- ///
- /// Default is false to preserve existing behavior. Enable this if you are making
- /// many requests with the same request types and reflection overhead is a bottleneck.
- ///
- ///
- public static bool EnableReflectionCache { get; set; }
- }
-}
diff --git a/Src/Support/Google.Apis.Tests/Apis/Requests/Parameters/ParameterUtilsTest.cs b/Src/Support/Google.Apis.Tests/Apis/Requests/Parameters/ParameterUtilsTest.cs
index 3327b08eec0..da3a7ff15be 100644
--- a/Src/Support/Google.Apis.Tests/Apis/Requests/Parameters/ParameterUtilsTest.cs
+++ b/Src/Support/Google.Apis.Tests/Apis/Requests/Parameters/ParameterUtilsTest.cs
@@ -14,6 +14,7 @@ You may obtain a copy of the License at
limitations under the License.
*/
+using Google;
using Google.Apis.Requests;
using Google.Apis.Requests.Parameters;
using Google.Apis.Util;
@@ -261,11 +262,11 @@ public void InitParametersWithExpansion_BooleanConversion()
[Fact]
public void InitParametersWithExpansion_CacheUsage()
{
- var originalState = ReflectionCacheSettings.EnableReflectionCache;
+ var originalState = ApplicationContext.EnableReflectionCache;
try
{
// Arrange - explicitly enable cache
- ReflectionCacheSettings.EnableReflectionCache = true;
+ ApplicationContext.EnableReflectionCache = true;
var request1 = new TestRequestWithScalars { Name = "test1", Id = 1 };
var request2 = new TestRequestWithScalars { Name = "test2", Id = 2 };
@@ -290,7 +291,7 @@ public void InitParametersWithExpansion_CacheUsage()
}
finally
{
- ReflectionCacheSettings.EnableReflectionCache = originalState;
+ ApplicationContext.EnableReflectionCache = originalState;
}
}
@@ -356,10 +357,10 @@ public void InitParametersWithExpansion_NullElementsInEnumerable()
public void IterateParameters_WorksWithBothCacheModes(bool enableCache)
{
// Arrange
- var originalState = ReflectionCacheSettings.EnableReflectionCache;
+ var originalState = ApplicationContext.EnableReflectionCache;
try
{
- ReflectionCacheSettings.EnableReflectionCache = enableCache;
+ ApplicationContext.EnableReflectionCache = enableCache;
var request = new TestRequestUrl()
{
FirstParam = "firstOne",
@@ -383,7 +384,7 @@ public void IterateParameters_WorksWithBothCacheModes(bool enableCache)
finally
{
// Restore original state
- ReflectionCacheSettings.EnableReflectionCache = originalState;
+ ApplicationContext.EnableReflectionCache = originalState;
}
}
}
diff --git a/Src/Support/Google.Apis.Tests/Apis/Utils/ReflectionCacheTest.cs b/Src/Support/Google.Apis.Tests/Apis/Utils/ReflectionCacheTest.cs
index 013c2bd1152..bcc790281ce 100644
--- a/Src/Support/Google.Apis.Tests/Apis/Utils/ReflectionCacheTest.cs
+++ b/Src/Support/Google.Apis.Tests/Apis/Utils/ReflectionCacheTest.cs
@@ -14,6 +14,7 @@ You may obtain a copy of the License at
limitations under the License.
*/
+using Google;
using Google.Apis.Util;
using System;
using System.Linq;
@@ -29,13 +30,13 @@ public class ReflectionCacheTest : IDisposable
public ReflectionCacheTest()
{
// Save original state to restore after each test
- _originalCacheState = ReflectionCacheSettings.EnableReflectionCache;
+ _originalCacheState = ApplicationContext.EnableReflectionCache;
}
public void Dispose()
{
// Restore original state after each test
- ReflectionCacheSettings.EnableReflectionCache = _originalCacheState;
+ ApplicationContext.EnableReflectionCache = _originalCacheState;
}
private class TestClass
{
@@ -52,7 +53,7 @@ private class TestClass
public void GetRequestParameterPropertiesWithAttribute_ReturnsPropertiesAndAttributes()
{
// Arrange - cache disabled (default behavior)
- ReflectionCacheSettings.EnableReflectionCache = false;
+ ApplicationContext.EnableReflectionCache = false;
// Act
var propertiesWithAttributes = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));
@@ -82,7 +83,7 @@ public void GetRequestParameterPropertiesWithAttribute_ReturnsPropertiesAndAttri
public void GetRequestParameterPropertiesWithAttribute_CachesResults()
{
// Arrange - explicitly enable cache
- ReflectionCacheSettings.EnableReflectionCache = true;
+ ApplicationContext.EnableReflectionCache = true;
// Act - Call twice
var result1 = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));
@@ -103,7 +104,7 @@ public void GetRequestParameterPropertiesWithAttribute_CachesResults()
public void GetRequestParameterProperties_ReturnsOnlyPropertiesWithAttribute()
{
// Arrange - cache disabled (default behavior)
- ReflectionCacheSettings.EnableReflectionCache = false;
+ ApplicationContext.EnableReflectionCache = false;
// Act
var properties = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));
@@ -121,7 +122,7 @@ public void GetRequestParameterPropertiesWithAttribute_RegressionTest_NoNewInsta
// This regression test ensures that repeated calls with cache enabled don't create new PropertyInfo or Attribute instances.
// Arrange - explicitly enable cache
- ReflectionCacheSettings.EnableReflectionCache = true;
+ ApplicationContext.EnableReflectionCache = true;
// Act - Get properties multiple times
var result1 = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));
@@ -146,7 +147,7 @@ public void GetRequestParameterPropertiesWithAttribute_RegressionTest_NoNewInsta
public void GetRequestParameterProperties_WithCacheDisabled_ReturnsDifferentInstanceOnSecondCall()
{
// Arrange
- ReflectionCacheSettings.EnableReflectionCache = false;
+ ApplicationContext.EnableReflectionCache = false;
// Act
var firstCall = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));
@@ -165,7 +166,7 @@ public void GetRequestParameterProperties_DefaultBehaviorIsNotCached()
// Arrange - don't set EnableReflectionCache, use default (false)
// (IDisposable restores the original state, so this tests a fresh-default scenario
// only when run as the first test; checking NotSame is sufficient)
- ReflectionCacheSettings.EnableReflectionCache = false;
+ ApplicationContext.EnableReflectionCache = false;
// Act
var firstCall = ReflectionCache.GetRequestParameterProperties(typeof(TestClass));