33
44using System . Security ;
55
6+ using Microsoft . VisualStudio . TestPlatform . MSTestAdapter . PlatformServices ;
67using Microsoft . VisualStudio . TestPlatform . ObjectModel ;
78using Microsoft . VisualStudio . TestTools . UnitTesting ;
89
@@ -15,11 +16,9 @@ internal class ReflectHelper : MarshalByRefObject
1516 private static readonly Lazy < ReflectHelper > InstanceValue = new ( ( ) => new ( ) ) ;
1617#pragma warning restore RS0030 // Do not use banned APIs
1718
18- // PERF: This was moved from Dictionary<MemberInfo, Dictionary<string, object>> to Concurrent<ICustomAttributeProvider, Attribute[]>
19- // storing an array allows us to store multiple attributes of the same type if we find them. It also has lower memory footprint, and is faster
20- // when we are going through the whole collection. Giving us overall better perf.
21- private readonly ConcurrentDictionary < ICustomAttributeProvider , Attribute [ ] > _attributeCache = [ ] ;
22-
19+ // PERF: Attribute caching is now centralized in ReflectionOperations._attributeCache.
20+ // ReflectHelper delegates to PlatformServiceProvider.Instance.ReflectionOperations.GetCustomAttributesCached
21+ // so that discovery and execution paths share a single cache, avoiding double memory usage.
2322 public static ReflectHelper Instance => InstanceValue . Value ;
2423
2524 /// <summary>
@@ -141,7 +140,7 @@ internal static bool MatchReturnType(MethodInfo method, Type returnType)
141140 /// <param name="categoryAttributeProvider">The member to inspect.</param>
142141 /// <param name="owningType">The reflected type that owns <paramref name="categoryAttributeProvider"/>.</param>
143142 /// <returns>Categories defined.</returns>
144- internal string [ ] GetTestCategories ( MemberInfo categoryAttributeProvider , Type owningType )
143+ internal static string [ ] GetTestCategories ( MemberInfo categoryAttributeProvider , Type owningType )
145144 {
146145 Attribute [ ] methodAttributes = GetCustomAttributesCached ( categoryAttributeProvider ) ;
147146 Attribute [ ] typeAttributes = GetCustomAttributesCached ( owningType ) ;
@@ -228,7 +227,7 @@ internal static bool IsDoNotParallelizeSet(Assembly assembly)
228227 /// </summary>
229228 /// <param name="testPropertyProvider">The member to inspect.</param>
230229 /// <returns>List of traits.</returns>
231- internal Trait [ ] GetTestPropertiesAsTraits ( MethodInfo testPropertyProvider )
230+ internal static Trait [ ] GetTestPropertiesAsTraits ( MethodInfo testPropertyProvider )
232231 {
233232 Attribute [ ] attributesFromMethod = GetCustomAttributesCached ( testPropertyProvider ) ;
234233 Attribute [ ] attributesFromClass = testPropertyProvider . ReflectedType is { } testClass ? GetCustomAttributesCached ( testClass ) : [ ] ;
@@ -307,7 +306,7 @@ internal Trait[] GetTestPropertiesAsTraits(MethodInfo testPropertyProvider)
307306 /// <param name="attributeProvider">The member to inspect.</param>
308307 /// <param name="action">The action to perform.</param>
309308 /// <param name="state">The state to pass to action.</param>
310- internal void PerformActionOnAttribute < TAttributeType , TState > ( ICustomAttributeProvider attributeProvider , Action < TAttributeType , TState ? > action , TState ? state )
309+ internal static void PerformActionOnAttribute < TAttributeType , TState > ( ICustomAttributeProvider attributeProvider , Action < TAttributeType , TState ? > action , TState ? state )
311310 where TAttributeType : Attribute
312311 {
313312 Attribute [ ] attributes = GetCustomAttributesCached ( attributeProvider ) ;
@@ -324,69 +323,20 @@ internal void PerformActionOnAttribute<TAttributeType, TState>(ICustomAttributeP
324323
325324 /// <summary>
326325 /// Gets and caches the attributes for the given type, or method.
326+ /// Delegates to <see cref="PlatformServiceProvider.Instance"/> so that
327+ /// discovery and execution share a single attribute cache.
327328 /// </summary>
328329 /// <param name="attributeProvider">The member to inspect.</param>
329330 /// <returns>attributes defined.</returns>
330- internal Attribute [ ] GetCustomAttributesCached ( ICustomAttributeProvider attributeProvider )
331- {
332- // If the information is cached, then use it otherwise populate the cache using
333- // the reflection APIs.
334- return _attributeCache . GetOrAdd ( attributeProvider , GetAttributes ) ;
335-
336- // We are avoiding func allocation here.
337- static Attribute [ ] GetAttributes ( ICustomAttributeProvider attributeProvider )
338- {
339- // Populate the cache
340- try
341- {
342- object [ ] ? attributes = NotCachedReflectionAccessor . GetCustomAttributesNotCached ( attributeProvider ) ;
343- return attributes is null ? [ ] : attributes as Attribute [ ] ?? [ .. attributes . Cast < Attribute > ( ) ] ;
344- }
345- catch ( Exception ex )
346- {
347- // Get the exception description
348- string description ;
349- try
350- {
351- // Can throw if the Message or StackTrace properties throw exceptions
352- description = ex . ToString ( ) ;
353- }
354- catch ( Exception ex2 )
355- {
356- description = string . Format ( CultureInfo . CurrentCulture , Resource . ExceptionOccuredWhileGettingTheExceptionDescription , ex . GetType ( ) . FullName , ex2 . GetType ( ) . FullName ) ; // ex.GetType().FullName +
357- }
331+ internal static Attribute [ ] GetCustomAttributesCached ( ICustomAttributeProvider attributeProvider )
332+ => PlatformServiceProvider . Instance . ReflectionOperations . GetCustomAttributesCached ( attributeProvider ) ;
358333
359- if ( PlatformServiceProvider . Instance . AdapterTraceLogger . IsWarningEnabled )
360- {
361- PlatformServiceProvider . Instance . AdapterTraceLogger . Warning ( Resource . FailedToGetCustomAttribute , attributeProvider . GetType ( ) . FullName ! , description ) ;
362- }
363-
364- return [ ] ;
365- }
366- }
367- }
368-
369- /// <summary>
370- /// Reflection helper that is accessing Reflection directly, and won't cache the results.
371- /// </summary>
372- internal static class NotCachedReflectionAccessor
334+ internal static /* for tests */ void ClearCache ( )
373335 {
374- /// <summary>
375- /// Get custom attributes on a member without cache. Be CAREFUL where you use this, repeatedly accessing reflection without caching the results degrades the performance.
376- /// </summary>
377- /// <param name="attributeProvider">Member for which attributes needs to be retrieved.</param>
378- /// <returns>All attributes of give type on member.</returns>
379- public static object [ ] ? GetCustomAttributesNotCached ( ICustomAttributeProvider attributeProvider )
336+ // Delegate to the shared cache in ReflectionOperations.
337+ if ( PlatformServiceProvider . Instance ? . ReflectionOperations is ReflectionOperations reflectionOperations )
380338 {
381- object [ ] attributesArray = attributeProvider is MemberInfo memberInfo
382- ? PlatformServiceProvider . Instance . ReflectionOperations . GetCustomAttributes ( memberInfo )
383- : PlatformServiceProvider . Instance . ReflectionOperations . GetCustomAttributes ( ( Assembly ) attributeProvider , typeof ( Attribute ) ) ;
384-
385- return attributesArray ; // TODO: Investigate if we rely on NRE
339+ reflectionOperations . ClearCache ( ) ;
386340 }
387341 }
388-
389- internal /* for tests */ void ClearCache ( )
390- // Tests manipulate the platform reflection provider, and we end up caching different attributes than the class / method actually has.
391- => _attributeCache . Clear ( ) ;
392342}
0 commit comments