diff --git a/src/libraries/System.Reflection.DispatchProxy/src/System.Reflection.DispatchProxy.csproj b/src/libraries/System.Reflection.DispatchProxy/src/System.Reflection.DispatchProxy.csproj
index 80891e3e0192b..babd7f14e4ed6 100644
--- a/src/libraries/System.Reflection.DispatchProxy/src/System.Reflection.DispatchProxy.csproj
+++ b/src/libraries/System.Reflection.DispatchProxy/src/System.Reflection.DispatchProxy.csproj
@@ -21,6 +21,7 @@
+
diff --git a/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs b/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs
index edf5ff293ab5e..b9e3fdac3051a 100644
--- a/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs
+++ b/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs
@@ -6,7 +6,8 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection.Emit;
-using System.Runtime.ExceptionServices;
+using System.Runtime.CompilerServices;
+using System.Runtime.Loader;
using System.Threading;
namespace System.Reflection
@@ -44,32 +45,18 @@ internal static class DispatchProxyGenerator
// It is the first field in the class and the first ctor parameter.
private const int MethodInfosFieldAndCtorParameterIndex = 0;
- // Proxies are requested for a pair of types: base type and interface type.
- // The generated proxy will subclass the given base type and implement the interface type.
- // We maintain a cache keyed by 'base type' containing a dictionary keyed by interface type,
- // containing the generated proxy type for that pair. There are likely to be few (maybe only 1)
- // base type in use for many interface types.
- // Note: this differs from Silverlight's RealProxy implementation which keys strictly off the
- // interface type. But this does not allow the same interface type to be used with more than a
- // single base type. The implementation here permits multiple interface types to be used with
- // multiple base types, and the generated proxy types will be unique.
- // This cache of generated types grows unbounded, one element per unique T/ProxyT pair.
- // This approach is used to prevent regenerating identical proxy types for identical T/Proxy pairs,
- // which would ultimately be a more expensive leak.
- // Proxy instances are not cached. Their lifetime is entirely owned by the caller of DispatchProxy.Create.
- private static readonly Dictionary> s_baseTypeAndInterfaceToGeneratedProxyType = new Dictionary>();
- private static readonly ProxyAssembly s_proxyAssembly = new ProxyAssembly();
+ // We group AssemblyBuilders by the ALC of the base type's assembly.
+ // This allows us to granularly unload generated proxy types.
+ private static readonly ConditionalWeakTable s_alcProxyAssemblyMap = new();
private static readonly MethodInfo s_dispatchProxyInvokeMethod = typeof(DispatchProxy).GetMethod("Invoke", BindingFlags.NonPublic | BindingFlags.Instance)!;
- private static readonly MethodInfo s_getTypeFromHandleMethod = typeof(Type).GetRuntimeMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) })!;
+ private static readonly MethodInfo s_getTypeFromHandleMethod = typeof(Type).GetMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) })!;
private static readonly MethodInfo s_makeGenericMethodMethod = GetGenericMethodMethodInfo();
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "MakeGenericMethod is safe here because the user code invoking the generic method will reference " +
"the GenericTypes being used, which will guarantee the requirements of the generic method.")]
- private static MethodInfo GetGenericMethodMethodInfo()
- {
- return typeof(MethodInfo).GetMethod("MakeGenericMethod", new Type[] { typeof(Type[]) })!;
- }
+ private static MethodInfo GetGenericMethodMethodInfo() =>
+ typeof(MethodInfo).GetMethod("MakeGenericMethod", new Type[] { typeof(Type[]) })!;
// Returns a new instance of a proxy the derives from 'baseType' and implements 'interfaceType'
internal static object CreateProxyInstance(
@@ -79,78 +66,12 @@ internal static object CreateProxyInstance(
Debug.Assert(baseType != null);
Debug.Assert(interfaceType != null);
- GeneratedTypeInfo proxiedType = GetProxyType(baseType, interfaceType);
- return Activator.CreateInstance(proxiedType.GeneratedType, new object[] { proxiedType.MethodInfos })!;
- }
-
- private static GeneratedTypeInfo GetProxyType(
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType,
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType)
- {
- lock (s_baseTypeAndInterfaceToGeneratedProxyType)
- {
- if (!s_baseTypeAndInterfaceToGeneratedProxyType.TryGetValue(baseType, out Dictionary? interfaceToProxy))
- {
- interfaceToProxy = new Dictionary();
- s_baseTypeAndInterfaceToGeneratedProxyType[baseType] = interfaceToProxy;
- }
-
- if (!interfaceToProxy.TryGetValue(interfaceType, out GeneratedTypeInfo? generatedProxy))
- {
- generatedProxy = GenerateProxyType(baseType, interfaceType);
- interfaceToProxy[interfaceType] = generatedProxy;
- }
-
- return generatedProxy;
- }
- }
-
- // Unconditionally generates a new proxy type derived from 'baseType' and implements 'interfaceType'
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2062:UnrecognizedReflectionPattern",
- Justification = "interfaceType is annotated as preserve All members, so any Types returned from GetInterfaces should be preserved as well once https://github.com/mono/linker/issues/1731 is fixed.")]
- private static GeneratedTypeInfo GenerateProxyType(
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType,
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType)
- {
- // Parameter validation is deferred until the point we need to create the proxy.
- // This prevents unnecessary overhead revalidating cached proxy types.
-
- // The interface type must be an interface, not a class
- if (!interfaceType.IsInterface)
- {
- // "T" is the generic parameter seen via the public contract
- throw new ArgumentException(SR.Format(SR.InterfaceType_Must_Be_Interface, interfaceType.FullName), "T");
- }
-
- // The base type cannot be sealed because the proxy needs to subclass it.
- if (baseType.IsSealed)
- {
- // "TProxy" is the generic parameter seen via the public contract
- throw new ArgumentException(SR.Format(SR.BaseType_Cannot_Be_Sealed, baseType.FullName), "TProxy");
- }
-
- // The base type cannot be abstract
- if (baseType.IsAbstract)
- {
- throw new ArgumentException(SR.Format(SR.BaseType_Cannot_Be_Abstract, baseType.FullName), "TProxy");
- }
-
- // The base type must have a public default ctor
- if (baseType.GetConstructor(Type.EmptyTypes) == null)
- {
- throw new ArgumentException(SR.Format(SR.BaseType_Must_Have_Default_Ctor, baseType.FullName), "TProxy");
- }
-
- // Create a type that derives from 'baseType' provided by caller
- ProxyBuilder pb = s_proxyAssembly.CreateProxy("generatedProxy", baseType);
-
- foreach (Type t in interfaceType.GetInterfaces())
- pb.AddInterfaceImpl(t);
+ AssemblyLoadContext? alc = AssemblyLoadContext.GetLoadContext(baseType.Assembly);
+ Debug.Assert(alc != null);
- pb.AddInterfaceImpl(interfaceType);
-
- GeneratedTypeInfo generatedProxyType = pb.CreateType();
- return generatedProxyType;
+ ProxyAssembly proxyAssembly = s_alcProxyAssemblyMap.GetValue(alc, static x => new ProxyAssembly(x));
+ GeneratedTypeInfo proxiedType = proxyAssembly.GetProxyType(baseType, interfaceType);
+ return Activator.CreateInstance(proxiedType.GeneratedType, new object[] { proxiedType.MethodInfos })!;
}
private sealed class GeneratedTypeInfo
@@ -170,16 +91,43 @@ public GeneratedTypeInfo(
private sealed class ProxyAssembly
{
+ // Proxies are requested for a pair of types: base type and interface type.
+ // The generated proxy will subclass the given base type and implement the interface type.
+ // We maintain a cache keyed by 'base type' containing a dictionary keyed by interface type,
+ // containing the generated proxy type for that pair. There are likely to be few (maybe only 1)
+ // base type in use for many interface types.
+ // Note: this differs from Silverlight's RealProxy implementation which keys strictly off the
+ // interface type. But this does not allow the same interface type to be used with more than a
+ // single base type. The implementation here permits multiple interface types to be used with
+ // multiple base types, and the generated proxy types will be unique.
+ // This cache of generated types grows unbounded, one element per unique T/ProxyT pair.
+ // This approach is used to prevent regenerating identical proxy types for identical T/Proxy pairs,
+ // which would ultimately be a more expensive leak.
+ // Proxy instances are not cached. Their lifetime is entirely owned by the caller of DispatchProxy.Create.
+ private readonly Dictionary> _baseTypeAndInterfaceToGeneratedProxyType = new Dictionary>();
+
private readonly AssemblyBuilder _ab;
private readonly ModuleBuilder _mb;
private int _typeId;
- private readonly HashSet _ignoresAccessAssemblyNames = new HashSet();
+ private readonly HashSet _ignoresAccessAssemblyNames = new HashSet();
private ConstructorInfo? _ignoresAccessChecksToAttributeConstructor;
- public ProxyAssembly()
+ public ProxyAssembly(AssemblyLoadContext alc)
{
- _ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("ProxyBuilder"), AssemblyBuilderAccess.Run);
+ string name;
+ if (alc == AssemblyLoadContext.Default)
+ {
+ name = "ProxyBuilder";
+ }
+ else
+ {
+ string? alcName = alc.Name;
+ name = string.IsNullOrEmpty(alcName) ? $"DispatchProxyTypes.{alc.GetHashCode()}" : $"DispatchProxyTypes.{alcName}";
+ }
+ AssemblyBuilderAccess builderAccess =
+ alc.IsCollectible ? AssemblyBuilderAccess.RunAndCollect : AssemblyBuilderAccess.Run;
+ _ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(name), builderAccess);
_mb = _ab.DefineDynamicModule("testmod");
}
@@ -199,9 +147,79 @@ internal ConstructorInfo IgnoresAccessChecksAttributeConstructor
}
}
+ public GeneratedTypeInfo GetProxyType(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType)
+ {
+ lock (_baseTypeAndInterfaceToGeneratedProxyType)
+ {
+ if (!_baseTypeAndInterfaceToGeneratedProxyType.TryGetValue(baseType, out Dictionary? interfaceToProxy))
+ {
+ interfaceToProxy = new Dictionary();
+ _baseTypeAndInterfaceToGeneratedProxyType[baseType] = interfaceToProxy;
+ }
+
+ if (!interfaceToProxy.TryGetValue(interfaceType, out GeneratedTypeInfo? generatedProxy))
+ {
+ generatedProxy = GenerateProxyType(baseType, interfaceType);
+ interfaceToProxy[interfaceType] = generatedProxy;
+ }
+
+ return generatedProxy;
+ }
+ }
+
+ // Unconditionally generates a new proxy type derived from 'baseType' and implements 'interfaceType'
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2062:UnrecognizedReflectionPattern",
+ Justification = "interfaceType is annotated as preserve All members, so any Types returned from GetInterfaces should be preserved as well once https://github.com/mono/linker/issues/1731 is fixed.")]
+ private GeneratedTypeInfo GenerateProxyType(
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType,
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType)
+ {
+ // Parameter validation is deferred until the point we need to create the proxy.
+ // This prevents unnecessary overhead revalidating cached proxy types.
+
+ // The interface type must be an interface, not a class
+ if (!interfaceType.IsInterface)
+ {
+ // "T" is the generic parameter seen via the public contract
+ throw new ArgumentException(SR.Format(SR.InterfaceType_Must_Be_Interface, interfaceType.FullName), "T");
+ }
+
+ // The base type cannot be sealed because the proxy needs to subclass it.
+ if (baseType.IsSealed)
+ {
+ // "TProxy" is the generic parameter seen via the public contract
+ throw new ArgumentException(SR.Format(SR.BaseType_Cannot_Be_Sealed, baseType.FullName), "TProxy");
+ }
+
+ // The base type cannot be abstract
+ if (baseType.IsAbstract)
+ {
+ throw new ArgumentException(SR.Format(SR.BaseType_Cannot_Be_Abstract, baseType.FullName), "TProxy");
+ }
+
+ // The base type must have a public default ctor
+ if (baseType.GetConstructor(Type.EmptyTypes) == null)
+ {
+ throw new ArgumentException(SR.Format(SR.BaseType_Must_Have_Default_Ctor, baseType.FullName), "TProxy");
+ }
+
+ // Create a type that derives from 'baseType' provided by caller
+ ProxyBuilder pb = CreateProxy("generatedProxy", baseType);
+
+ foreach (Type t in interfaceType.GetInterfaces())
+ pb.AddInterfaceImpl(t);
+
+ pb.AddInterfaceImpl(interfaceType);
+
+ GeneratedTypeInfo generatedProxyType = pb.CreateType();
+ return generatedProxyType;
+ }
+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern",
Justification = "Only the parameterless ctor is referenced on proxyBaseType. Other members can be trimmed if unused.")]
- public ProxyBuilder CreateProxy(
+ private ProxyBuilder CreateProxy(
string name,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type proxyBaseType)
{
@@ -231,10 +249,9 @@ internal void EnsureTypeIsVisible(Type type)
if (!type.IsVisible)
{
string assemblyName = type.Assembly.GetName().Name!;
- if (!_ignoresAccessAssemblyNames.Contains(assemblyName))
+ if (_ignoresAccessAssemblyNames.Add(assemblyName))
{
GenerateInstanceOfIgnoresAccessChecksToAttribute(assemblyName);
- _ignoresAccessAssemblyNames.Add(assemblyName);
}
}
}
@@ -278,11 +295,11 @@ private void Complete()
ILGenerator il = cb.GetILGenerator();
// chained ctor call
- ConstructorInfo? baseCtor = _proxyBaseType.GetConstructor(Type.EmptyTypes);
+ ConstructorInfo baseCtor = _proxyBaseType.GetConstructor(Type.EmptyTypes)!;
Debug.Assert(baseCtor != null);
il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Call, baseCtor!);
+ il.Emit(OpCodes.Call, baseCtor);
// store all the fields
for (int i = 0; i < args.Length; i++)
@@ -520,64 +537,7 @@ private MethodBuilder AddMethodImpl(MethodInfo mi, int methodInfoIndex)
return mdb;
}
- // TypeCode does not exist in ProjectK or ProjectN.
- // This lookup method was copied from PortableLibraryThunks\Internal\PortableLibraryThunks\System\TypeThunks.cs
- // but returns the integer value equivalent to its TypeCode enum.
- private static int GetTypeCode(Type? type)
- {
- if (type == null)
- return 0; // TypeCode.Empty;
-
- if (type == typeof(bool))
- return 3; // TypeCode.Boolean;
-
- if (type == typeof(char))
- return 4; // TypeCode.Char;
-
- if (type == typeof(sbyte))
- return 5; // TypeCode.SByte;
-
- if (type == typeof(byte))
- return 6; // TypeCode.Byte;
-
- if (type == typeof(short))
- return 7; // TypeCode.Int16;
-
- if (type == typeof(ushort))
- return 8; // TypeCode.UInt16;
-
- if (type == typeof(int))
- return 9; // TypeCode.Int32;
-
- if (type == typeof(uint))
- return 10; // TypeCode.UInt32;
-
- if (type == typeof(long))
- return 11; // TypeCode.Int64;
-
- if (type == typeof(ulong))
- return 12; // TypeCode.UInt64;
-
- if (type == typeof(float))
- return 13; // TypeCode.Single;
-
- if (type == typeof(double))
- return 14; // TypeCode.Double;
-
- if (type == typeof(decimal))
- return 15; // TypeCode.Decimal;
-
- if (type == typeof(DateTime))
- return 16; // TypeCode.DateTime;
-
- if (type == typeof(string))
- return 18; // TypeCode.String;
-
- if (type.IsEnum)
- return GetTypeCode(Enum.GetUnderlyingType(type));
-
- return 1; // TypeCode.Object;
- }
+ private static int GetTypeCode(Type type) => (int)Type.GetTypeCode(type);
private static readonly OpCode[] s_convOpCodes = new OpCode[] {
OpCodes.Nop, //Empty = 0,
diff --git a/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs b/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs
index cb5edc26d5d5f..9118e69a2a586 100644
--- a/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs
+++ b/src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs
@@ -6,6 +6,8 @@
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
+using System.Runtime.CompilerServices;
+using System.Runtime.Loader;
using System.Text;
using Xunit;
@@ -622,5 +624,61 @@ private static void testRefOutInInvocation(Action invocation,
Assert.Equal(expected, result);
}
+
+ private static TestType_IHelloService CreateTestHelloProxy() =>
+ DispatchProxy.Create();
+
+ [Fact]
+ public static void Test_Unloadability()
+ {
+ if (typeof(DispatchProxyTests).Assembly.Location == "")
+ return;
+
+ WeakReference wr = CreateProxyInUnloadableAlc();
+
+ for (int i = 0; i < 10 && wr.IsAlive; i++)
+ {
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ }
+
+ Assert.False(wr.IsAlive, "The ALC could not be unloaded.");
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static WeakReference CreateProxyInUnloadableAlc()
+ {
+ var alc = new AssemblyLoadContext(nameof(Test_Unloadability), true);
+ alc.LoadFromAssemblyPath(typeof(DispatchProxyTests).Assembly.Location)
+ .GetType(typeof(DispatchProxyTests).FullName, true)
+ .GetMethod(nameof(CreateTestHelloProxy), BindingFlags.Static | BindingFlags.NonPublic)
+ .Invoke(null, null);
+ return new WeakReference(alc);
+ }
+ }
+
+ [Fact]
+ public static void Test_Multiple_AssemblyLoadContexts()
+ {
+ if (typeof(DispatchProxyTests).Assembly.Location == "")
+ return;
+
+ object proxyDefaultAlc = CreateTestDispatchProxy(typeof(TestDispatchProxy));
+ Assert.True(proxyDefaultAlc.GetType().IsAssignableTo(typeof(TestDispatchProxy)));
+
+ Type proxyCustomAlcType =
+ new AssemblyLoadContext(nameof(Test_Multiple_AssemblyLoadContexts))
+ .LoadFromAssemblyPath(typeof(DispatchProxyTests).Assembly.Location)
+ .GetType(typeof(TestDispatchProxy).FullName, true);
+
+ object proxyCustomAlc = CreateTestDispatchProxy(proxyCustomAlcType);
+ Assert.True(proxyCustomAlc.GetType().IsAssignableTo(proxyCustomAlcType));
+
+ static object CreateTestDispatchProxy(Type type) =>
+ typeof(DispatchProxy)
+ .GetMethod(nameof(DispatchProxy.Create))
+ // It has to be a type shared in both ALCs.
+ .MakeGenericMethod(typeof(IDisposable), type)
+ .Invoke(null, null);
+ }
}
}