From 7d9f6ca9a541102e24e7747a7d87babcad9e5e4a Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Tue, 30 Dec 2025 22:47:58 +0100 Subject: [PATCH 1/8] Extract `CreateAssembly` from `ModuleScope.CreateModule` --- src/Castle.Core/DynamicProxy/ModuleScope.cs | 27 ++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/ModuleScope.cs b/src/Castle.Core/DynamicProxy/ModuleScope.cs index 578360e9b..421ba3de0 100644 --- a/src/Castle.Core/DynamicProxy/ModuleScope.cs +++ b/src/Castle.Core/DynamicProxy/ModuleScope.cs @@ -1,4 +1,4 @@ -// Copyright 2004-2021 Castle Project - http://www.castleproject.org/ +// Copyright 2004-2026 Castle Project - http://www.castleproject.org/ // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -311,8 +311,25 @@ internal ModuleBuilder ObtainDynamicModuleWithWeakName() private ModuleBuilder CreateModule(bool signStrongName) { - var assemblyName = GetAssemblyName(signStrongName); + var assemblyBuilder = CreateAssembly(signStrongName); var moduleName = signStrongName ? StrongNamedModuleName : WeakNamedModuleName; +#if FEATURE_APPDOMAIN + if (savePhysicalAssembly) + { + var module = assemblyBuilder.DefineDynamicModule(moduleName, moduleName, false); + return module; + } + else +#endif + { + var module = assemblyBuilder.DefineDynamicModule(moduleName); + return module; + } + } + + private AssemblyBuilder CreateAssembly(bool signStrongName) + { + var assemblyName = GetAssemblyName(signStrongName); #if FEATURE_APPDOMAIN if (savePhysicalAssembly) { @@ -336,8 +353,7 @@ private ModuleBuilder CreateModule(bool signStrongName) GetType()); throw new ArgumentException(message, e); } - var module = assemblyBuilder.DefineDynamicModule(moduleName, moduleName, false); - return module; + return assemblyBuilder; } else #endif @@ -349,8 +365,7 @@ private ModuleBuilder CreateModule(bool signStrongName) var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); #endif - var module = assemblyBuilder.DefineDynamicModule(moduleName); - return module; + return assemblyBuilder; } } From bc6901738ce58174fd7956c7c80e2d4b6c75b644 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Tue, 30 Dec 2025 22:56:41 +0100 Subject: [PATCH 2/8] Simplify conditions in these two methods Note that `catch`-ing strong-naming failures in all cases isn't just a refactoring, but a functional change. It seems appropriate, and more correct than what we had before, becaus creating a dynamic assembly and strong-naming it are really separate concerns. --- src/Castle.Core/DynamicProxy/ModuleScope.cs | 64 ++++++++++----------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/ModuleScope.cs b/src/Castle.Core/DynamicProxy/ModuleScope.cs index 421ba3de0..1e1867f89 100644 --- a/src/Castle.Core/DynamicProxy/ModuleScope.cs +++ b/src/Castle.Core/DynamicProxy/ModuleScope.cs @@ -313,59 +313,53 @@ private ModuleBuilder CreateModule(bool signStrongName) { var assemblyBuilder = CreateAssembly(signStrongName); var moduleName = signStrongName ? StrongNamedModuleName : WeakNamedModuleName; -#if FEATURE_APPDOMAIN +#if NET462_OR_GREATER if (savePhysicalAssembly) { - var module = assemblyBuilder.DefineDynamicModule(moduleName, moduleName, false); - return module; + return assemblyBuilder.DefineDynamicModule(moduleName, moduleName, false); } else -#endif { - var module = assemblyBuilder.DefineDynamicModule(moduleName); - return module; + return assemblyBuilder.DefineDynamicModule(moduleName); } +#else + return assemblyBuilder.DefineDynamicModule(moduleName); +#endif } private AssemblyBuilder CreateAssembly(bool signStrongName) { var assemblyName = GetAssemblyName(signStrongName); -#if FEATURE_APPDOMAIN - if (savePhysicalAssembly) + try { - AssemblyBuilder assemblyBuilder; - try +#if NET462_OR_GREATER + if (savePhysicalAssembly) { - assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( - assemblyName, AssemblyBuilderAccess.RunAndSave, signStrongName ? StrongNamedModuleDirectory : WeakNamedModuleDirectory); + return AppDomain.CurrentDomain.DefineDynamicAssembly( + assemblyName, + AssemblyBuilderAccess.RunAndSave, + signStrongName ? StrongNamedModuleDirectory : WeakNamedModuleDirectory); } - catch (ArgumentException e) + else { - if (signStrongName == false && e.StackTrace.Contains("ComputePublicKey") == false) - { - // I have no idea what that could be - throw; - } - var message = string.Format( - "There was an error creating dynamic assembly for your proxies - you don't have permissions " + - "required to sign the assembly. To workaround it you can enforce generating non-signed assembly " + - "only when creating {0}. Alternatively ensure that your account has all the required permissions.", - GetType()); - throw new ArgumentException(message, e); + return AppDomain.CurrentDomain.DefineDynamicAssembly( + assemblyName, + AssemblyBuilderAccess.Run); } - return assemblyBuilder; - } - else -#endif - { -#if FEATURE_APPDOMAIN - var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( - assemblyName, AssemblyBuilderAccess.Run); #else - var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + return AssemblyBuilder.DefineDynamicAssembly( + assemblyName, + AssemblyBuilderAccess.Run); #endif - - return assemblyBuilder; + } + catch (ArgumentException e) when (signStrongName || e.StackTrace?.Contains("ComputePublicKey") == true) + { + var message = string.Format( + "There was an error creating dynamic assembly for your proxies - you don't have permissions " + + "required to sign the assembly. To workaround it you can enforce generating non-signed assembly " + + "only when creating {0}. Alternatively ensure that your account has all the required permissions.", + GetType()); + throw new ArgumentException(message, e); } } From b1211e82d828a5c7b94ac236f0592e9541509f86 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Tue, 30 Dec 2025 23:17:23 +0100 Subject: [PATCH 3/8] Add `ModuleScope.BuildType` method --- .../DynamicProxy/Generators/Emitters/ClassEmitter.cs | 4 ++-- src/Castle.Core/DynamicProxy/ModuleScope.cs | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/ClassEmitter.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/ClassEmitter.cs index bc87365dc..df24467ff 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/ClassEmitter.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/ClassEmitter.cs @@ -1,4 +1,4 @@ -// Copyright 2004-2025 Castle Project - http://www.castleproject.org/ +// Copyright 2004-2026 Castle Project - http://www.castleproject.org/ // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -412,7 +412,7 @@ public Type BuildType() builder.Generate(); } - var type = typeBuilder.CreateTypeInfo(); + var type = moduleScope.BuildType(typeBuilder); return type; } diff --git a/src/Castle.Core/DynamicProxy/ModuleScope.cs b/src/Castle.Core/DynamicProxy/ModuleScope.cs index 1e1867f89..dc09f5458 100644 --- a/src/Castle.Core/DynamicProxy/ModuleScope.cs +++ b/src/Castle.Core/DynamicProxy/ModuleScope.cs @@ -553,5 +553,10 @@ internal TypeBuilder DefineType(bool inSignedModulePreferably, string name, Type var module = ObtainDynamicModule(disableSignedModule == false && inSignedModulePreferably); return module.DefineType(name, flags); } + + internal Type BuildType(TypeBuilder typeBuilder) + { + return typeBuilder.CreateTypeInfo()!; + } } } From ad005182c2ba6c40d0af2c41b9a4b665dfeb783d Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Tue, 30 Dec 2025 23:18:50 +0100 Subject: [PATCH 4/8] Make `CreateAssembly`, `DefineType` & `BuildType` overridable --- src/Castle.Core/DynamicProxy/ModuleScope.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/ModuleScope.cs b/src/Castle.Core/DynamicProxy/ModuleScope.cs index dc09f5458..a3481a821 100644 --- a/src/Castle.Core/DynamicProxy/ModuleScope.cs +++ b/src/Castle.Core/DynamicProxy/ModuleScope.cs @@ -327,7 +327,7 @@ private ModuleBuilder CreateModule(bool signStrongName) #endif } - private AssemblyBuilder CreateAssembly(bool signStrongName) + internal virtual AssemblyBuilder CreateAssembly(bool signStrongName) { var assemblyName = GetAssemblyName(signStrongName); try @@ -363,7 +363,7 @@ private AssemblyBuilder CreateAssembly(bool signStrongName) } } - private AssemblyName GetAssemblyName(bool signStrongName) + internal AssemblyName GetAssemblyName(bool signStrongName) { var assemblyName = new AssemblyName { Name = signStrongName ? strongAssemblyName : weakAssemblyName @@ -548,13 +548,13 @@ public void LoadAssemblyIntoCache(Assembly assembly) } #endif - internal TypeBuilder DefineType(bool inSignedModulePreferably, string name, TypeAttributes flags) + internal virtual TypeBuilder DefineType(bool inSignedModulePreferably, string name, TypeAttributes flags) { var module = ObtainDynamicModule(disableSignedModule == false && inSignedModulePreferably); return module.DefineType(name, flags); } - internal Type BuildType(TypeBuilder typeBuilder) + internal virtual Type BuildType(TypeBuilder typeBuilder) { return typeBuilder.CreateTypeInfo()!; } From 327b0682b49dab9822141465c7c8281291c7550e Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Wed, 31 Dec 2025 01:40:08 +0100 Subject: [PATCH 5/8] Implement `PersistentProxyBuilder` for .NET 9+ --- src/Castle.Core/DynamicProxy/ModuleScope.cs | 9 ++ .../DynamicProxy/PersistentProxyBuilder.cs | 47 ++++++- ...PersistentProxyBuilderAssemblyEventArgs.cs | 53 ++++++++ .../PersistentProxyBuilderModuleScope.cs | 118 ++++++++++++++++++ 4 files changed, 222 insertions(+), 5 deletions(-) create mode 100644 src/Castle.Core/DynamicProxy/PersistentProxyBuilderAssemblyEventArgs.cs create mode 100644 src/Castle.Core/DynamicProxy/PersistentProxyBuilderModuleScope.cs diff --git a/src/Castle.Core/DynamicProxy/ModuleScope.cs b/src/Castle.Core/DynamicProxy/ModuleScope.cs index a3481a821..908b91a44 100644 --- a/src/Castle.Core/DynamicProxy/ModuleScope.cs +++ b/src/Castle.Core/DynamicProxy/ModuleScope.cs @@ -381,6 +381,15 @@ internal AssemblyName GetAssemblyName(bool signStrongName) return assemblyName; } + internal void ResetModules() + { + lock (moduleLocker) + { + moduleBuilder = null; + moduleBuilderWithStrongName = null; + } + } + #if FEATURE_ASSEMBLYBUILDER_SAVE /// /// Saves the generated assembly with the name and directory information given when this instance was created (or with diff --git a/src/Castle.Core/DynamicProxy/PersistentProxyBuilder.cs b/src/Castle.Core/DynamicProxy/PersistentProxyBuilder.cs index 4c591132d..13bc29cd3 100644 --- a/src/Castle.Core/DynamicProxy/PersistentProxyBuilder.cs +++ b/src/Castle.Core/DynamicProxy/PersistentProxyBuilder.cs @@ -1,4 +1,4 @@ -// Copyright 2004-2021 Castle Project - http://www.castleproject.org/ +// Copyright 2004-2026 Castle Project - http://www.castleproject.org/ // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,12 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if FEATURE_ASSEMBLYBUILDER_SAVE - #nullable enable namespace Castle.DynamicProxy { + using System; + using System.Reflection; + +#if NET462_OR_GREATER + /// /// ProxyBuilder that persists the generated type. /// @@ -46,6 +49,40 @@ public PersistentProxyBuilder() : base(new ModuleScope(true)) return ModuleScope.SaveAssembly(); } } -} -#endif \ No newline at end of file +#elif NET9_0_OR_GREATER + + /// + /// that allows you to persist proxy types. + /// Each generated proxy type will be placed in its own separate assembly. + /// + public sealed class PersistentProxyBuilder : DefaultProxyBuilder + { + /// + /// Initializes a new instance of the class. + /// + public PersistentProxyBuilder() + : this(new PersistentProxyBuilderModuleScope()) + { + } + + private PersistentProxyBuilder(PersistentProxyBuilderModuleScope scope) + : base(scope) + { + scope.AssemblyCreated += OnAssemblyCreated; + } + + /// + /// Raised when a new proxy type assembly has been created and loaded into the runtime. + /// + public event EventHandler? AssemblyCreated; + + private void OnAssemblyCreated(Assembly assembly, byte[] assemblyBytes) + { + AssemblyCreated?.Invoke(this, new PersistentProxyBuilderAssemblyEventArgs(assembly, assemblyBytes)); + } + } + +#endif + +} \ No newline at end of file diff --git a/src/Castle.Core/DynamicProxy/PersistentProxyBuilderAssemblyEventArgs.cs b/src/Castle.Core/DynamicProxy/PersistentProxyBuilderAssemblyEventArgs.cs new file mode 100644 index 000000000..d37bbaea2 --- /dev/null +++ b/src/Castle.Core/DynamicProxy/PersistentProxyBuilderAssemblyEventArgs.cs @@ -0,0 +1,53 @@ +// Copyright 2004-2026 Castle Project - http://www.castleproject.org/ +// +// 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. + +#if NET9_0_OR_GREATER + +#nullable enable + +namespace Castle.DynamicProxy +{ + using System; + using System.Reflection; + + /// + /// Provides data for the event. + /// + public sealed class PersistentProxyBuilderAssemblyEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The assembly that has been created and loaded into the runtime. + /// The raw bytes of the created assembly (can be saved as a DLL file). + internal PersistentProxyBuilderAssemblyEventArgs(Assembly assembly, byte[] assemblyBytes) + { + Assembly = assembly; + AssemblyBytes = assemblyBytes; + } + + /// + /// The assembly that has been created and loaded into the runtime. + /// + public Assembly Assembly { get; } + + /// + /// The raw bytes of the created assembly (can be saved as a DLL file). + /// + /// + public byte[] AssemblyBytes { get; } + } +} + +#endif diff --git a/src/Castle.Core/DynamicProxy/PersistentProxyBuilderModuleScope.cs b/src/Castle.Core/DynamicProxy/PersistentProxyBuilderModuleScope.cs new file mode 100644 index 000000000..bf514c875 --- /dev/null +++ b/src/Castle.Core/DynamicProxy/PersistentProxyBuilderModuleScope.cs @@ -0,0 +1,118 @@ +// Copyright 2004-2026 Castle Project - http://www.castleproject.org/ +// +// 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. + +#if NET9_0_OR_GREATER + +#nullable enable + +namespace Castle.DynamicProxy +{ + using System; + using System.IO; + using System.Reflection; + using System.Reflection.Emit; + using System.Runtime.Loader; + + /// + /// A specialization used by , + /// based on . + /// + /// + /// Dynamic types created with cannot be activated. + /// In order for them to be usable, the dynamic assembly has to first be written out + /// and then loaded by the runtime as a regular assembly. This implies that either all proxy types + /// must be generated ahead of time (if they are to be placed in a single assembly); or, that + /// every proxy type gets its own assembly (if they should be immediately activatable). + /// This class opts for the latter approach. + /// + internal sealed class PersistentProxyBuilderModuleScope : ModuleScope + { + private bool usingStrongNamedModule; + + public PersistentProxyBuilderModuleScope() + : base(savePhysicalAssembly: false, disableSignedModule: false) + { + usingStrongNamedModule = false; + } + + public event Action? AssemblyCreated; + + internal override AssemblyBuilder CreateAssembly(bool signStrongName) + { + var assemblyName = GetAssemblyName(signStrongName); + return new PersistedAssemblyBuilder(assemblyName, coreAssembly: typeof(object).Assembly); + } + + internal override TypeBuilder DefineType(bool inSignedModulePreferably, string name, TypeAttributes flags) + { + TypeBuilder typeBuilder; + + if (IsAuxiliaryType(name) == false) + { + // The requested `TypeBuilder` is for a main proxy type. + // Each of those gets placed in its own assembly. + ResetModules(); + typeBuilder = base.DefineType(inSignedModulePreferably, name, flags); + usingStrongNamedModule = typeBuilder.Module == StrongNamedModule; + } + else + { + // The requested `TypeBuilder` is for an extra type needed for + // the main proxy type. (Currently, those are always invocation types.) + // They need to be placed in the same assembly as the main proxy type, + // otherwise the runtime won't find them during actual use. + // + // TODO: Currently, strong-named modules seem to be preferred for + // invocation types even when the main proxy type is in a non-strong-named + // module. Should find out why that is. Also, ignoring that preference + // may cause other problems. + typeBuilder = base.DefineType(usingStrongNamedModule, name, flags); + } + + return typeBuilder; + } + + internal override Type BuildType(TypeBuilder typeBuilder) + { + Type type = typeBuilder.CreateTypeInfo(); + var typeName = type.FullName!; + + if (IsAuxiliaryType(typeName) == false) + { + var assemblyBuilder = (PersistedAssemblyBuilder)typeBuilder.Assembly; + + using var stream = new MemoryStream(); + assemblyBuilder.Save(stream); + + stream.Seek(0, SeekOrigin.Begin); + var alc = new AssemblyLoadContext(name: null); + var assembly = alc.LoadFromStream(stream); + + stream.Seek(0, SeekOrigin.Begin); + AssemblyCreated?.Invoke(assembly, stream.GetBuffer()); + + type = assembly.GetType(typeName)!; + } + + return type; + } + + private bool IsAuxiliaryType(string name) + { + return name.StartsWith("Castle.Proxies.Invocations."); + } + } +} + +#endif From 2d073b974b127ca92f8135380e80d016bc4864ba Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Wed, 31 Dec 2025 03:16:04 +0100 Subject: [PATCH 6/8] Add tests for `PersistentProxyBuilder` --- .../PersistentProxyBuilderTestCase.cs | 65 +++++++++++++++++-- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/src/Castle.Core.Tests/DynamicProxy.Tests/PersistentProxyBuilderTestCase.cs b/src/Castle.Core.Tests/DynamicProxy.Tests/PersistentProxyBuilderTestCase.cs index 64e95bf6e..67b216a39 100644 --- a/src/Castle.Core.Tests/DynamicProxy.Tests/PersistentProxyBuilderTestCase.cs +++ b/src/Castle.Core.Tests/DynamicProxy.Tests/PersistentProxyBuilderTestCase.cs @@ -1,4 +1,4 @@ -// Copyright 2004-2021 Castle Project - http://www.castleproject.org/ +// Copyright 2004-2026 Castle Project - http://www.castleproject.org/ // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,14 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if FEATURE_ASSEMBLYBUILDER_SAVE - namespace Castle.DynamicProxy.Tests { using System; + using System.Collections.Generic; using System.IO; + using System.Linq; + using System.Reflection; + + using Castle.DynamicProxy.Tests.Interfaces; + using NUnit.Framework; +#if NET462_OR_GREATER + [TestFixture] public class PersistentProxyBuilderTestCase { @@ -44,6 +50,55 @@ public void PersistentProxyBuilder_SavesSignedFile() Assert.IsTrue(path.EndsWith(ModuleScope.DEFAULT_FILE_NAME)); } } -} -#endif \ No newline at end of file +#elif NET9_0_OR_GREATER + + [TestFixture] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] + public class PersistentProxyBuilderTestCase + { + private List assemblies; + private PersistentProxyBuilder builder; + + [SetUp] + public void SetupProxyBuilder() + { + assemblies = new List(); + builder = new PersistentProxyBuilder(); + builder.AssemblyCreated += (object _, PersistentProxyBuilderAssemblyEventArgs e) => + { + assemblies.Add(e.Assembly); + }; + } + + [Test] + public void SavesOneAssemblyPerProxiedType() + { + var oneProxyType = builder.CreateInterfaceProxyTypeWithoutTarget(typeof(IOne), Type.EmptyTypes, ProxyGenerationOptions.Default); + Assert.AreEqual(1, assemblies.Count); + + var twoProxyType = builder.CreateInterfaceProxyTypeWithoutTarget(typeof(ITwo), Type.EmptyTypes, ProxyGenerationOptions.Default); + Assert.AreEqual(2, assemblies.Count); + + var oneAssembly = assemblies[0]; + var twoAssembly = assemblies[1]; + Assert.AreSame(oneAssembly, oneProxyType.Assembly); + Assert.AreSame(twoAssembly, twoProxyType.Assembly); + Assert.AreNotSame(oneAssembly, twoAssembly); + } + + [Test] + public void TypeCacheWorks() + { + var proxyType1 = builder.CreateClassProxyType(typeof(object), Type.EmptyTypes, ProxyGenerationOptions.Default); + var proxyType2 = builder.CreateClassProxyType(typeof(object), Type.EmptyTypes, ProxyGenerationOptions.Default); + + Assert.AreEqual(1, assemblies.Count); + Assert.AreSame(proxyType1, proxyType2); + Assert.AreSame(proxyType1.Assembly, proxyType2.Assembly); + } + } + +#endif + +} From 34bad204201e6348b7ece48f60cb3302d81313a3 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Wed, 31 Dec 2025 03:48:02 +0100 Subject: [PATCH 7/8] Update `ref/` contract file for `net9.0` --- ref/Castle.Core-net9.0.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ref/Castle.Core-net9.0.cs b/ref/Castle.Core-net9.0.cs index 7b416367c..ff07f7f7d 100644 --- a/ref/Castle.Core-net9.0.cs +++ b/ref/Castle.Core-net9.0.cs @@ -2585,6 +2585,16 @@ public ModuleScope(bool savePhysicalAssembly, bool disableSignedModule, string s public string WeakNamedModuleName { get; } public static byte[] GetKeyPair() { } } + public sealed class PersistentProxyBuilder : Castle.DynamicProxy.DefaultProxyBuilder + { + public PersistentProxyBuilder() { } + public event System.EventHandler? AssemblyCreated; + } + public sealed class PersistentProxyBuilderAssemblyEventArgs : System.EventArgs + { + public System.Reflection.Assembly Assembly { get; } + public byte[] AssemblyBytes { get; } + } public class ProxyGenerationOptions { public static readonly Castle.DynamicProxy.ProxyGenerationOptions Default; From 737ef786d62a4ccc634b62be5c0a93088e2e71ba Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Wed, 31 Dec 2025 01:57:27 +0100 Subject: [PATCH 8/8] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2be2b6c03..e0f1a5922 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Breaking Changes: Enhancements: - Minimally improved support for methods having `ref struct` parameter and return types, such as `Span`: Intercepting such methods caused the runtime to throw `InvalidProgramException` and `NullReferenceException` due to forbidden conversions of `ref struct` values when transferring them into & out of `IInvocation` instances. To prevent these exceptions from being thrown, such values now get replaced with `null` in `IInvocation`, and with `default` values in return values and `out` arguments. When proceeding to a target, the target methods likewise receive such nullified values. (@stakx, #665) +- Restore ability on .NET 9 and later to save dynamic assemblies to disk using `PersistentProxyBuilder` (@stakx, #718) - Dependencies were updated Bugfixes: