diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs
index 25f29ed8e596eb..359653c22d716d 100644
--- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs
+++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
using System.Reflection.Metadata;
[assembly: MetadataUpdateHandler(typeof(RuntimeTypeMetadataUpdateHandler))]
@@ -10,16 +11,76 @@ namespace System.Reflection.Metadata
/// Metadata update handler used to clear a Type's reflection cache in response to a metadata update notification.
internal static class RuntimeTypeMetadataUpdateHandler
{
- public static void BeforeUpdate(Type? type)
+ /// Clear type caches in response to an update notification.
+ /// The specific types to be cleared, or null to clear everything.
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Clearing the caches on a Type isn't affected if a Type is trimmed, or has any of its members trimmed.")]
+ public static void BeforeUpdate(Type[]? types)
{
- if (type is RuntimeType rt)
+ if (RequiresClearingAllTypes(types))
{
- rt.ClearCache();
+ // TODO: This should ideally be in a QCall in the runtime. As written here:
+ // - This materializes all types in the app, creating RuntimeTypes for them.
+ // - This force loads all assembly dependencies, which might not work well for packages that may depend on resolve events firing at the right moment.
+ // - This does not cover instantiated generic types.
+
+ // Clear the caches on all loaded types.
+ foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
+ {
+ if (!SkipAssembly(assembly))
+ {
+ try
+ {
+ foreach (Type type in assembly.GetTypes())
+ {
+ ClearCache(type);
+ }
+ }
+ catch (ReflectionTypeLoadException) { }
+ }
+ }
+ }
+ else
+ {
+ // Clear the caches on just the specified types.
+ foreach (Type type in types)
+ {
+ ClearCache(type);
+ }
+ }
+ }
+
+ /// When clearing all types, determines whether we should skip types from the specified assembly.
+ private static bool SkipAssembly(Assembly assembly) =>
+ // Ideally we'd skip all of the core libraries, which can't be edited.
+ // But we can easily skip corelib.
+ typeof(object).Assembly == assembly;
+
+ /// Clears the cache on the specified type.
+ private static void ClearCache(Type type) => (type as RuntimeType)?.ClearCache();
+
+ /// Determines whether we need to clear the caches on all types or just the ones specified.
+ /// The types to examine.
+ /// true if we need to clear all types; false if we can clear just the ones specified.
+ private static bool RequiresClearingAllTypes([NotNullWhen(false)] Type[]? types)
+ {
+ // If we were handed null, assume we need to clear everything.
+ if (types is null)
+ {
+ return true;
+ }
+
+ // If any type isn't sealed, we may need to clear a derived type,
+ // so clear everything.
+ foreach (Type type in types)
+ {
+ if (!type.IsSealed)
+ {
+ return true;
+ }
}
- // TODO: https://github.com/dotnet/runtime/issues/50938
- // Do we need to clear the cache on other types, e.g. ones derived from this one?
- // Do we need to clear a cache on any other kinds of types?
+ // We were handed types, and they were all sealed, so we can just clear them.
+ return false;
}
}
}
diff --git a/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs
index f295be7f73faf0..4136b2e1d2d566 100644
--- a/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs
+++ b/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs
@@ -1,10 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Tests;
using Xunit;
namespace System.Reflection.Tests
{
+ [Collection(nameof(NoParallelTests))]
public class ReflectionCacheTests
{
[Fact]
@@ -21,17 +23,28 @@ public void GetMethod_MultipleCalls_SameObjects()
[ActiveIssue("https://github.com/dotnet/runtime/issues/50978", TestRuntimes.Mono)]
[Fact]
- public void GetMethod_MultipleCalls_ClearCache_DifferentObjects()
+ public void InvokeBeforeUpdate_NoExceptions()
{
- Type updateHandler = typeof(Type).Assembly.GetType("System.Reflection.Metadata.RuntimeTypeMetadataUpdateHandler", throwOnError: true, ignoreCase: false);
- MethodInfo beforeUpdate = updateHandler.GetMethod("BeforeUpdate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, new[] { typeof(Type) });
- Assert.NotNull(beforeUpdate);
+ Action beforeUpdate = GetBeforeUpdateMethod();
+ beforeUpdate(null);
+ beforeUpdate(new Type[0]);
+ beforeUpdate(new Type[] { typeof(ReflectionCacheTests) });
+ beforeUpdate(new Type[] { typeof(string), typeof(int) });
+ }
+
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/50978", TestRuntimes.Mono)]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void GetMethod_MultipleCalls_ClearCache_DifferentObjects(bool justSpecificType)
+ {
+ Action beforeUpdate = GetBeforeUpdateMethod();
MethodInfo mi1 = typeof(ReflectionCacheTests).GetMethod(nameof(GetMethod_MultipleCalls_ClearCache_DifferentObjects));
Assert.NotNull(mi1);
Assert.Equal(nameof(GetMethod_MultipleCalls_ClearCache_DifferentObjects), mi1.Name);
- beforeUpdate.Invoke(null, new object[] { typeof(ReflectionCacheTests) });
+ beforeUpdate(justSpecificType ? new[] { typeof(ReflectionCacheTests) } : null);
MethodInfo mi2 = typeof(ReflectionCacheTests).GetMethod(nameof(GetMethod_MultipleCalls_ClearCache_DifferentObjects));
Assert.NotNull(mi2);
@@ -39,5 +52,13 @@ public void GetMethod_MultipleCalls_ClearCache_DifferentObjects()
Assert.NotSame(mi1, mi2);
}
+
+ private static Action GetBeforeUpdateMethod()
+ {
+ Type updateHandler = typeof(Type).Assembly.GetType("System.Reflection.Metadata.RuntimeTypeMetadataUpdateHandler", throwOnError: true, ignoreCase: false);
+ MethodInfo beforeUpdate = updateHandler.GetMethod("BeforeUpdate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, new[] { typeof(Type[]) });
+ Assert.NotNull(beforeUpdate);
+ return beforeUpdate.CreateDelegate>();
+ }
}
}