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));