From cb3fbd69b4c71e02793359f60a778526d6c9b1fc Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sun, 7 Dec 2025 19:17:32 +0100 Subject: [PATCH 1/3] Add `net9.0` TFM for `PersistedAssemblyBuilder` --- src/Castle.Core/Castle.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Castle.Core/Castle.Core.csproj b/src/Castle.Core/Castle.Core.csproj index fa705deaf..687288258 100644 --- a/src/Castle.Core/Castle.Core.csproj +++ b/src/Castle.Core/Castle.Core.csproj @@ -3,7 +3,7 @@ - net8.0;net462;netstandard2.0 + net9.0;net8.0;net462;netstandard2.0 From dfa3f13161a869386e3395b12cb97e630ed0d503 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sun, 7 Dec 2025 19:18:04 +0100 Subject: [PATCH 2/3] Disable `DiagnosticsLogger` (`EventLog` no longer available on .NET 9+) --- .../Core.Tests/Logging/DiagnosticsLoggerTestCase.cs | 4 ++++ src/Castle.Core/Core/Logging/DiagnosticsLogger.cs | 4 ++++ src/Castle.Core/Core/Logging/DiagnosticsLoggerFactory.cs | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/src/Castle.Core.Tests/Core.Tests/Logging/DiagnosticsLoggerTestCase.cs b/src/Castle.Core.Tests/Core.Tests/Logging/DiagnosticsLoggerTestCase.cs index 029c901be..063c9ae5a 100644 --- a/src/Castle.Core.Tests/Core.Tests/Logging/DiagnosticsLoggerTestCase.cs +++ b/src/Castle.Core.Tests/Core.Tests/Logging/DiagnosticsLoggerTestCase.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if !NET9_0_OR_GREATER + namespace Castle.Core.Logging.Tests { using System; @@ -101,3 +103,5 @@ public void SimpleUsage() } } } + +#endif diff --git a/src/Castle.Core/Core/Logging/DiagnosticsLogger.cs b/src/Castle.Core/Core/Logging/DiagnosticsLogger.cs index bf591efc3..7674f44b1 100644 --- a/src/Castle.Core/Core/Logging/DiagnosticsLogger.cs +++ b/src/Castle.Core/Core/Logging/DiagnosticsLogger.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if !NET9_0_OR_GREATER + namespace Castle.Core.Logging { using System; @@ -147,3 +149,5 @@ private static EventLogEntryType TranslateLevel(LoggerLevel level) } } } + +#endif \ No newline at end of file diff --git a/src/Castle.Core/Core/Logging/DiagnosticsLoggerFactory.cs b/src/Castle.Core/Core/Logging/DiagnosticsLoggerFactory.cs index 97ee575e0..4deb0bdd2 100644 --- a/src/Castle.Core/Core/Logging/DiagnosticsLoggerFactory.cs +++ b/src/Castle.Core/Core/Logging/DiagnosticsLoggerFactory.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if !NET9_0_OR_GREATER + namespace Castle.Core.Logging { using System; @@ -39,3 +41,5 @@ public override ILogger Create(string name, LoggerLevel level) } } } + +#endif From feac078ef223cca2a5738174eb92922f700a025b Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sun, 7 Dec 2025 19:29:00 +0100 Subject: [PATCH 3/3] Enable `PersistentProxyBuilder` on .NET 9+ (albeit without type cache) --- .../DynamicProxy/DefaultProxyBuilder.cs | 43 ++++++++++++++++--- .../Generators/BaseClassProxyGenerator.cs | 2 + .../Generators/BaseInterfaceProxyGenerator.cs | 3 +- .../Generators/BaseProxyGenerator.cs | 2 +- src/Castle.Core/DynamicProxy/ModuleScope.cs | 30 ++++++++++++- .../DynamicProxy/PersistentProxyBuilder.cs | 16 ++++++- 6 files changed, 86 insertions(+), 10 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/DefaultProxyBuilder.cs b/src/Castle.Core/DynamicProxy/DefaultProxyBuilder.cs index 703e4aafb..930c14d6a 100644 --- a/src/Castle.Core/DynamicProxy/DefaultProxyBuilder.cs +++ b/src/Castle.Core/DynamicProxy/DefaultProxyBuilder.cs @@ -20,6 +20,11 @@ namespace Castle.DynamicProxy using System.Collections.Generic; using System.Linq; using System.Reflection; +#if NET9_0_OR_GREATER + using System.IO; + using System.Reflection.Emit; + using System.Runtime.Loader; +#endif using Castle.Core.Internal; using Castle.Core.Logging; @@ -30,9 +35,14 @@ namespace Castle.DynamicProxy /// public class DefaultProxyBuilder : IProxyBuilder { - private readonly ModuleScope scope; + private ModuleScope scope; private ILogger logger = NullLogger.Instance; +#if NET9_0_OR_GREATER + protected ModuleScope lastScope; + protected MemoryStream lastAssemblyGenerated; +#endif + /// /// Initializes a new instance of the class with new . /// @@ -68,7 +78,7 @@ public Type CreateClassProxyType(Type classToProxy, Type[]? additionalInterfaces AssertValidMixins(options, nameof(options)); var generator = new ClassProxyGenerator(scope, classToProxy, additionalInterfacesToProxy, options) { Logger = logger }; - return generator.GetProxyType(); + return ToInstantiableType(generator.GetProxyType(), generator); } public Type CreateClassProxyTypeWithTarget(Type classToProxy, Type[]? additionalInterfacesToProxy, @@ -80,7 +90,7 @@ public Type CreateClassProxyTypeWithTarget(Type classToProxy, Type[]? additional var generator = new ClassProxyWithTargetGenerator(scope, classToProxy, additionalInterfacesToProxy, options) { Logger = logger }; - return generator.GetProxyType(); + return ToInstantiableType(generator.GetProxyType(), generator); } public Type CreateInterfaceProxyTypeWithTarget(Type interfaceToProxy, Type[]? additionalInterfacesToProxy, @@ -92,7 +102,7 @@ public Type CreateInterfaceProxyTypeWithTarget(Type interfaceToProxy, Type[]? ad AssertValidMixins(options, nameof(options)); var generator = new InterfaceProxyWithTargetGenerator(scope, interfaceToProxy, additionalInterfacesToProxy, targetType, options) { Logger = logger }; - return generator.GetProxyType(); + return ToInstantiableType(generator.GetProxyType(), generator); } public Type CreateInterfaceProxyTypeWithTargetInterface(Type interfaceToProxy, Type[]? additionalInterfacesToProxy, @@ -103,7 +113,7 @@ public Type CreateInterfaceProxyTypeWithTargetInterface(Type interfaceToProxy, T AssertValidMixins(options, nameof(options)); var generator = new InterfaceProxyWithTargetInterfaceGenerator(scope, interfaceToProxy, additionalInterfacesToProxy, interfaceToProxy, options) { Logger = logger }; - return generator.GetProxyType(); + return ToInstantiableType(generator.GetProxyType(), generator); } public Type CreateInterfaceProxyTypeWithoutTarget(Type interfaceToProxy, Type[]? additionalInterfacesToProxy, @@ -114,7 +124,28 @@ public Type CreateInterfaceProxyTypeWithoutTarget(Type interfaceToProxy, Type[]? AssertValidMixins(options, nameof(options)); var generator = new InterfaceProxyWithoutTargetGenerator(scope, interfaceToProxy, additionalInterfacesToProxy, typeof(object), options) { Logger = logger }; - return generator.GetProxyType(); + return ToInstantiableType(generator.GetProxyType(), generator); + } + + private Type ToInstantiableType(Type type, BaseProxyGenerator generator) + { +#if NET9_0_OR_GREATER + if (type.Assembly is PersistedAssemblyBuilder persistedAssemblyBuilder) + { + lastAssemblyGenerated?.Dispose(); + var stream = new MemoryStream(); + persistedAssemblyBuilder.Save(stream); + stream.Seek(0, SeekOrigin.Begin); + var assembly = AssemblyLoadContext.Default.LoadFromStream(stream); + type = assembly.GetType(type.FullName!)!; + lastAssemblyGenerated = stream; + lastScope = scope; + scope = scope.Recycle(); + } + + generator.InitializeStaticFields(type); +#endif + return type; } private void AssertValidMixins(ProxyGenerationOptions options, string paramName) diff --git a/src/Castle.Core/DynamicProxy/Generators/BaseClassProxyGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/BaseClassProxyGenerator.cs index 02f601238..55df5d020 100644 --- a/src/Castle.Core/DynamicProxy/Generators/BaseClassProxyGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/BaseClassProxyGenerator.cs @@ -102,7 +102,9 @@ protected sealed override Type GenerateType(string name, INamingScope namingScop // Crosses fingers and build type var proxyType = emitter.BuildType(); +#if !NET9_0_OR_GREATER InitializeStaticFields(proxyType); +#endif return proxyType; } diff --git a/src/Castle.Core/DynamicProxy/Generators/BaseInterfaceProxyGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/BaseInterfaceProxyGenerator.cs index 84e6b1186..36823344b 100644 --- a/src/Castle.Core/DynamicProxy/Generators/BaseInterfaceProxyGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/BaseInterfaceProxyGenerator.cs @@ -137,8 +137,9 @@ protected override Type GenerateType(string typeName, INamingScope namingScope) // Crosses fingers and build type var generatedType = emitter.BuildType(); - +#if !NET9_0_OR_GREATER InitializeStaticFields(generatedType); +#endif return generatedType; } diff --git a/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs index 2d790409d..3fa9d993c 100644 --- a/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/BaseProxyGenerator.cs @@ -384,7 +384,7 @@ protected void HandleExplicitlyPassedProxyTargetAccessor(ICollection targe } } - protected void InitializeStaticFields(Type builtType) + internal void InitializeStaticFields(Type builtType) { builtType.SetStaticField("proxyGenerationOptions", BindingFlags.NonPublic, ProxyGenerationOptions); } diff --git a/src/Castle.Core/DynamicProxy/ModuleScope.cs b/src/Castle.Core/DynamicProxy/ModuleScope.cs index af17ae2eb..e84193b60 100644 --- a/src/Castle.Core/DynamicProxy/ModuleScope.cs +++ b/src/Castle.Core/DynamicProxy/ModuleScope.cs @@ -150,6 +150,13 @@ internal INamingScope NamingScope internal SynchronizedDictionary TypeCache => typeCache; +#if NET9_0_OR_GREATER + internal ModuleScope Recycle() + { + return new ModuleScope(savePhysicalAssembly, disableSignedModule, strongAssemblyName, strongModulePath, weakAssemblyName, weakModulePath); + } +#endif + /// /// Gets the key pair used to sign the strong-named assembly generated by this . /// @@ -336,6 +343,14 @@ private ModuleBuilder CreateModule(bool signStrongName) return module; } else +#elif NET9_0_OR_GREATER + if (savePhysicalAssembly) + { + AssemblyBuilder assemblyBuilder = new PersistedAssemblyBuilder(assemblyName, typeof(object).Assembly); + var module = assemblyBuilder.DefineDynamicModule(moduleName); + return module; + } + else #endif { #if FEATURE_APPDOMAIN @@ -368,7 +383,7 @@ private AssemblyName GetAssemblyName(bool signStrongName) return assemblyName; } -#if FEATURE_ASSEMBLYBUILDER_SAVE +#if FEATURE_ASSEMBLYBUILDER_SAVE || NET9_0_OR_GREATER /// /// Saves the generated assembly with the name and directory information given when this instance was created (or with /// the and current directory if none was given). @@ -450,7 +465,11 @@ private AssemblyName GetAssemblyName(bool signStrongName) } assemblyBuilder = (AssemblyBuilder)StrongNamedModule.Assembly; assemblyFileName = StrongNamedModuleName; +#if NET9_0_OR_GREATER + assemblyFilePath = weakModulePath; +#else assemblyFilePath = StrongNamedModule.FullyQualifiedName; +#endif } else { @@ -460,7 +479,11 @@ private AssemblyName GetAssemblyName(bool signStrongName) } assemblyBuilder = (AssemblyBuilder)WeakNamedModule.Assembly; assemblyFileName = WeakNamedModuleName; +#if NET9_0_OR_GREATER + assemblyFilePath = weakModulePath; +#else assemblyFilePath = WeakNamedModule.FullyQualifiedName; +#endif } if (File.Exists(assemblyFilePath)) @@ -472,7 +495,12 @@ private AssemblyName GetAssemblyName(bool signStrongName) AddCacheMappings(assemblyBuilder); #endif +#if FEATURE_ASSEMBLYBUILDER_SAVE assemblyBuilder.Save(assemblyFileName); +#elif NET9_0_OR_GREATER + var persistedAssemblyBuilder = (PersistedAssemblyBuilder)assemblyBuilder; + persistedAssemblyBuilder.Save(assemblyFilePath); +#endif return assemblyFilePath; } #endif diff --git a/src/Castle.Core/DynamicProxy/PersistentProxyBuilder.cs b/src/Castle.Core/DynamicProxy/PersistentProxyBuilder.cs index 4c591132d..bcd11f1c2 100644 --- a/src/Castle.Core/DynamicProxy/PersistentProxyBuilder.cs +++ b/src/Castle.Core/DynamicProxy/PersistentProxyBuilder.cs @@ -12,12 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if FEATURE_ASSEMBLYBUILDER_SAVE +#if FEATURE_ASSEMBLYBUILDER_SAVE || NET9_0_OR_GREATER #nullable enable namespace Castle.DynamicProxy { +#if NET9_0_OR_GREATER + using System.IO; +#endif + /// /// ProxyBuilder that persists the generated type. /// @@ -43,7 +47,17 @@ public PersistentProxyBuilder() : base(new ModuleScope(true)) /// public string? SaveAssembly() { +#if NET9_0_OR_GREATER + var assemblyPath = lastScope.WeakNamedModule != null ? lastScope.WeakNamedModuleName : lastScope.StrongNamedModuleName; + using var file = File.Create(assemblyPath); + lastAssemblyGenerated.Seek(0, SeekOrigin.Begin); + lastAssemblyGenerated.CopyTo(file); + file.Flush(); + file.Close(); + return assemblyPath; +#else return ModuleScope.SaveAssembly(); +#endif } } }