From 94d6fbaa25de0844baa73537ff47d0af1417ca3d Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Fri, 23 Jan 2026 10:57:18 +0100 Subject: [PATCH 1/8] Remove unused `ConvertToArgumentReference` method overload --- .../Generators/Emitters/ArgumentsUtil.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs index 388ceb028..28c920e8f 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.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. @@ -34,18 +34,6 @@ public static ArgumentReference[] ConvertToArgumentReference(Type[] args) return arguments; } - public static ArgumentReference[] ConvertToArgumentReference(ParameterInfo[] args) - { - var arguments = new ArgumentReference[args.Length]; - - for (var i = 0; i < args.Length; ++i) - { - arguments[i] = new ArgumentReference(args[i].ParameterType); - } - - return arguments; - } - public static IExpression[] ConvertToArgumentReferenceExpression(ParameterInfo[] args) { var arguments = new IExpression[args.Length]; From 808332d14624bdc2efbcbd8a537908f7e2392597 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Fri, 23 Jan 2026 10:58:41 +0100 Subject: [PATCH 2/8] Change `ConvertToArgumentReferenceExpression` name & return type --- .../DynamicProxy/Generators/Emitters/ArgumentsUtil.cs | 4 ++-- .../DynamicProxy/Generators/ForwardingMethodGenerator.cs | 4 ++-- .../Generators/OptionallyForwardingMethodGenerator.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs index 28c920e8f..63938b86f 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs @@ -34,9 +34,9 @@ public static ArgumentReference[] ConvertToArgumentReference(Type[] args) return arguments; } - public static IExpression[] ConvertToArgumentReferenceExpression(ParameterInfo[] args) + public static ArgumentReference[] ConvertToArgumentReference(ParameterInfo[] args) { - var arguments = new IExpression[args.Length]; + var arguments = new ArgumentReference[args.Length]; for (var i = 0; i < args.Length; ++i) { diff --git a/src/Castle.Core/DynamicProxy/Generators/ForwardingMethodGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/ForwardingMethodGenerator.cs index 769b0cffe..1c6d221d5 100644 --- a/src/Castle.Core/DynamicProxy/Generators/ForwardingMethodGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/ForwardingMethodGenerator.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. @@ -33,7 +33,7 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C INamingScope namingScope) { var target = getTarget(@class, MethodToOverride); - var arguments = ArgumentsUtil.ConvertToArgumentReferenceExpression(MethodToOverride.GetParameters()); + var arguments = ArgumentsUtil.ConvertToArgumentReference(MethodToOverride.GetParameters()); emitter.CodeBuilder.AddStatement(new ReturnStatement( new MethodInvocationExpression( diff --git a/src/Castle.Core/DynamicProxy/Generators/OptionallyForwardingMethodGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/OptionallyForwardingMethodGenerator.cs index 86359be05..47dbe8cc0 100644 --- a/src/Castle.Core/DynamicProxy/Generators/OptionallyForwardingMethodGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/OptionallyForwardingMethodGenerator.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. @@ -50,7 +50,7 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C private IStatement IfNotNull(IExpression target) { var statements = new BlockStatement(); - var arguments = ArgumentsUtil.ConvertToArgumentReferenceExpression(MethodToOverride.GetParameters()); + var arguments = ArgumentsUtil.ConvertToArgumentReference(MethodToOverride.GetParameters()); statements.AddStatement(new ReturnStatement( new MethodInvocationExpression( From 3d4735b46c967bb68619288c2f13b5333fd72c75 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Fri, 23 Jan 2026 11:02:06 +0100 Subject: [PATCH 3/8] Inline single-use `InitializeArgumentsByPosition` method --- .../Generators/Emitters/ArgumentsUtil.cs | 14 +++----------- .../Generators/Emitters/MethodEmitter.cs | 5 ++--- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs index 63938b86f..bbf70158c 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs @@ -22,13 +22,14 @@ namespace Castle.DynamicProxy.Generators.Emitters internal abstract class ArgumentsUtil { - public static ArgumentReference[] ConvertToArgumentReference(Type[] args) + public static ArgumentReference[] ConvertToArgumentReference(Type[] args, bool isStatic) { var arguments = new ArgumentReference[args.Length]; + var offset = isStatic ? 0 : 1; for (var i = 0; i < args.Length; ++i) { - arguments[i] = new ArgumentReference(args[i]); + arguments[i] = new ArgumentReference(args[i], i + offset); } return arguments; @@ -68,14 +69,5 @@ public static Type[] InitializeAndConvert(ArgumentReference[] args) return types; } - - public static void InitializeArgumentsByPosition(ArgumentReference[] args, bool isStatic) - { - var offset = isStatic ? 0 : 1; - for (var i = 0; i < args.Length; ++i) - { - args[i].Position = i + offset; - } - } } } \ No newline at end of file diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.cs index b77c62b2d..b940cd12e 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.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. @@ -115,8 +115,7 @@ public void DefineCustomAttribute(CustomAttributeBuilder attribute) public void SetParameters(Type[] paramTypes) { builder.SetParameters(paramTypes); - arguments = ArgumentsUtil.ConvertToArgumentReference(paramTypes); - ArgumentsUtil.InitializeArgumentsByPosition(arguments, MethodBuilder.IsStatic); + arguments = ArgumentsUtil.ConvertToArgumentReference(paramTypes, MethodBuilder.IsStatic); } public virtual void Generate() From e3a02cc5e429d347f93d5a0708a8df8d165dd60c Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Fri, 23 Jan 2026 11:06:39 +0100 Subject: [PATCH 4/8] DynamicProxy does not emit static methods --- .../DynamicProxy.Tests/ClassEmitterTestCase.cs | 14 +------------- .../Generators/Emitters/ArgumentsUtil.cs | 5 ++--- .../Generators/Emitters/MethodEmitter.cs | 4 +++- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/Castle.Core.Tests/DynamicProxy.Tests/ClassEmitterTestCase.cs b/src/Castle.Core.Tests/DynamicProxy.Tests/ClassEmitterTestCase.cs index cef0481cc..c36101e60 100644 --- a/src/Castle.Core.Tests/DynamicProxy.Tests/ClassEmitterTestCase.cs +++ b/src/Castle.Core.Tests/DynamicProxy.Tests/ClassEmitterTestCase.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. @@ -41,18 +41,6 @@ public void AutomaticDefaultConstructorGenerationWithClosedGenericType() Activator.CreateInstance(t); } - [Test] - public void StaticMethodArguments() - { - ClassEmitter emitter = new ClassEmitter(generator.ProxyBuilder.ModuleScope, "Foo", typeof (List), - Type.EmptyTypes); - MethodEmitter methodEmitter = emitter.CreateMethod("StaticMethod", MethodAttributes.Public | MethodAttributes.Static, - typeof (string), typeof (string)); - methodEmitter.CodeBuilder.AddStatement(new ReturnStatement(methodEmitter.Arguments[0])); - Type t = emitter.BuildType(); - Assert.AreEqual("five", t.GetMethod("StaticMethod").Invoke(null, new object[] {"five"})); - } - [Test] public void InstanceMethodArguments() { diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs index bbf70158c..8e7c19b4b 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs @@ -22,14 +22,13 @@ namespace Castle.DynamicProxy.Generators.Emitters internal abstract class ArgumentsUtil { - public static ArgumentReference[] ConvertToArgumentReference(Type[] args, bool isStatic) + public static ArgumentReference[] ConvertToArgumentReference(Type[] args) { var arguments = new ArgumentReference[args.Length]; - var offset = isStatic ? 0 : 1; for (var i = 0; i < args.Length; ++i) { - arguments[i] = new ArgumentReference(args[i], i + offset); + arguments[i] = new ArgumentReference(args[i], i + 1); } return arguments; diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.cs index b940cd12e..066a6726d 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.cs @@ -115,7 +115,9 @@ public void DefineCustomAttribute(CustomAttributeBuilder attribute) public void SetParameters(Type[] paramTypes) { builder.SetParameters(paramTypes); - arguments = ArgumentsUtil.ConvertToArgumentReference(paramTypes, MethodBuilder.IsStatic); + + Debug.Assert(MethodBuilder.IsStatic == false, "The following call to `ConvertToArgumentReference` assumes the presence of a `this` parameter when assigning parameter positions."); + arguments = ArgumentsUtil.ConvertToArgumentReference(paramTypes); } public virtual void Generate() From b2ac75f77feff95a18aff8fba03d2c11caa568cd Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Fri, 23 Jan 2026 11:11:29 +0100 Subject: [PATCH 5/8] Prevent memory alloc in case of no args/params The compiler transforms the C# 14 collection expression `[]` used here to `Array.Empty()`, so this won't cause repeated array allocations. --- .../DynamicProxy/Generators/Emitters/ArgumentsUtil.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs index 8e7c19b4b..f40874cb3 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs @@ -24,6 +24,8 @@ internal abstract class ArgumentsUtil { public static ArgumentReference[] ConvertToArgumentReference(Type[] args) { + if (args.Length == 0) return []; + var arguments = new ArgumentReference[args.Length]; for (var i = 0; i < args.Length; ++i) @@ -36,6 +38,8 @@ public static ArgumentReference[] ConvertToArgumentReference(Type[] args) public static ArgumentReference[] ConvertToArgumentReference(ParameterInfo[] args) { + if (args.Length == 0) return []; + var arguments = new ArgumentReference[args.Length]; for (var i = 0; i < args.Length; ++i) @@ -48,6 +52,8 @@ public static ArgumentReference[] ConvertToArgumentReference(ParameterInfo[] arg public static Type[] GetTypes(ParameterInfo[] parameters) { + if (parameters.Length == 0) return []; + var types = new Type[parameters.Length]; for (var i = 0; i < parameters.Length; i++) { @@ -58,6 +64,8 @@ public static Type[] GetTypes(ParameterInfo[] parameters) public static Type[] InitializeAndConvert(ArgumentReference[] args) { + if (args.Length == 0) return []; + var types = new Type[args.Length]; for (var i = 0; i < args.Length; ++i) From 071edb4f18d16846de856bb6c0f7fdc4515364bf Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Fri, 23 Jan 2026 12:17:07 +0100 Subject: [PATCH 6/8] Use more consistent names --- .../Generators/Emitters/ArgumentsUtil.cs | 32 +++++++++---------- .../Generators/Emitters/MethodEmitter.cs | 4 +-- .../Generators/ForwardingMethodGenerator.cs | 2 +- .../OptionallyForwardingMethodGenerator.cs | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs index f40874cb3..85628a592 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs @@ -22,29 +22,29 @@ namespace Castle.DynamicProxy.Generators.Emitters internal abstract class ArgumentsUtil { - public static ArgumentReference[] ConvertToArgumentReference(Type[] args) + public static ArgumentReference[] ConvertToArgumentReferences(Type[] parameterTypes) { - if (args.Length == 0) return []; + if (parameterTypes.Length == 0) return []; - var arguments = new ArgumentReference[args.Length]; + var arguments = new ArgumentReference[parameterTypes.Length]; - for (var i = 0; i < args.Length; ++i) + for (var i = 0; i < parameterTypes.Length; ++i) { - arguments[i] = new ArgumentReference(args[i], i + 1); + arguments[i] = new ArgumentReference(parameterTypes[i], i + 1); } return arguments; } - public static ArgumentReference[] ConvertToArgumentReference(ParameterInfo[] args) + public static ArgumentReference[] ConvertToArgumentReferences(ParameterInfo[] parameters) { - if (args.Length == 0) return []; + if (parameters.Length == 0) return []; - var arguments = new ArgumentReference[args.Length]; + var arguments = new ArgumentReference[parameters.Length]; - for (var i = 0; i < args.Length; ++i) + for (var i = 0; i < parameters.Length; ++i) { - arguments[i] = new ArgumentReference(args[i].ParameterType, i + 1); + arguments[i] = new ArgumentReference(parameters[i].ParameterType, i + 1); } return arguments; @@ -62,16 +62,16 @@ public static Type[] GetTypes(ParameterInfo[] parameters) return types; } - public static Type[] InitializeAndConvert(ArgumentReference[] args) + public static Type[] InitializeAndConvert(ArgumentReference[] arguments) { - if (args.Length == 0) return []; + if (arguments.Length == 0) return []; - var types = new Type[args.Length]; + var types = new Type[arguments.Length]; - for (var i = 0; i < args.Length; ++i) + for (var i = 0; i < arguments.Length; ++i) { - args[i].Position = i + 1; - types[i] = args[i].Type; + arguments[i].Position = i + 1; + types[i] = arguments[i].Type; } return types; diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.cs index 066a6726d..b52bee5c1 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.cs @@ -116,8 +116,8 @@ public void SetParameters(Type[] paramTypes) { builder.SetParameters(paramTypes); - Debug.Assert(MethodBuilder.IsStatic == false, "The following call to `ConvertToArgumentReference` assumes the presence of a `this` parameter when assigning parameter positions."); - arguments = ArgumentsUtil.ConvertToArgumentReference(paramTypes); + Debug.Assert(MethodBuilder.IsStatic == false, "The following call to `ConvertToArgumentReferences` assumes the presence of a `this` parameter when assigning parameter positions."); + arguments = ArgumentsUtil.ConvertToArgumentReferences(paramTypes); } public virtual void Generate() diff --git a/src/Castle.Core/DynamicProxy/Generators/ForwardingMethodGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/ForwardingMethodGenerator.cs index 1c6d221d5..52ef7736a 100644 --- a/src/Castle.Core/DynamicProxy/Generators/ForwardingMethodGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/ForwardingMethodGenerator.cs @@ -33,7 +33,7 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C INamingScope namingScope) { var target = getTarget(@class, MethodToOverride); - var arguments = ArgumentsUtil.ConvertToArgumentReference(MethodToOverride.GetParameters()); + var arguments = ArgumentsUtil.ConvertToArgumentReferences(MethodToOverride.GetParameters()); emitter.CodeBuilder.AddStatement(new ReturnStatement( new MethodInvocationExpression( diff --git a/src/Castle.Core/DynamicProxy/Generators/OptionallyForwardingMethodGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/OptionallyForwardingMethodGenerator.cs index 47dbe8cc0..4ebc688ab 100644 --- a/src/Castle.Core/DynamicProxy/Generators/OptionallyForwardingMethodGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/OptionallyForwardingMethodGenerator.cs @@ -50,7 +50,7 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C private IStatement IfNotNull(IExpression target) { var statements = new BlockStatement(); - var arguments = ArgumentsUtil.ConvertToArgumentReference(MethodToOverride.GetParameters()); + var arguments = ArgumentsUtil.ConvertToArgumentReferences(MethodToOverride.GetParameters()); statements.AddStatement(new ReturnStatement( new MethodInvocationExpression( From 80e6594679115a27b692ed7bbe7b0e20a6d7243d Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Fri, 23 Jan 2026 12:24:19 +0100 Subject: [PATCH 7/8] Switch to extension method syntax --- .../Contributors/CompositeTypeContributor.cs | 8 +-- .../Generators/Emitters/ArgumentsUtil.cs | 58 ++++++++++--------- .../Generators/Emitters/MethodEmitter.cs | 8 +-- .../Generators/ForwardingMethodGenerator.cs | 2 +- .../OptionallyForwardingMethodGenerator.cs | 2 +- 5 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Contributors/CompositeTypeContributor.cs b/src/Castle.Core/DynamicProxy/Contributors/CompositeTypeContributor.cs index 6a165d0d9..7c93efc11 100644 --- a/src/Castle.Core/DynamicProxy/Contributors/CompositeTypeContributor.cs +++ b/src/Castle.Core/DynamicProxy/Contributors/CompositeTypeContributor.cs @@ -139,14 +139,14 @@ private void ImplementMethod(MetaMethod method, ClassEmitter @class, protected static Type[] GetCacheKeyTypes(MetaMethod method) { - Type[] argumentTypes = ArgumentsUtil.GetTypes(method.MethodOnTarget.GetParameters()); - if (argumentTypes.Length < 1) + Type[] parameterTypes = method.MethodOnTarget.GetParameters().GetTypes(); + if (parameterTypes.Length < 1) { return new[] { method.MethodOnTarget.ReturnType }; } - var types = new Type[argumentTypes.Length + 1]; + var types = new Type[parameterTypes.Length + 1]; types[0] = method.MethodOnTarget.ReturnType; - argumentTypes.CopyTo(types, 1); + parameterTypes.CopyTo(types, 1); return types; } diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs index 85628a592..461f46db2 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs @@ -20,46 +20,52 @@ namespace Castle.DynamicProxy.Generators.Emitters using Castle.DynamicProxy.Generators.Emitters.SimpleAST; - internal abstract class ArgumentsUtil + internal static class ArgumentsUtil { - public static ArgumentReference[] ConvertToArgumentReferences(Type[] parameterTypes) + extension(ParameterInfo[] parameters) { - if (parameterTypes.Length == 0) return []; - - var arguments = new ArgumentReference[parameterTypes.Length]; - - for (var i = 0; i < parameterTypes.Length; ++i) + public ArgumentReference[] ConvertToArgumentReferences() { - arguments[i] = new ArgumentReference(parameterTypes[i], i + 1); - } + if (parameters.Length == 0) return []; - return arguments; - } + var arguments = new ArgumentReference[parameters.Length]; - public static ArgumentReference[] ConvertToArgumentReferences(ParameterInfo[] parameters) - { - if (parameters.Length == 0) return []; + for (var i = 0; i < parameters.Length; ++i) + { + arguments[i] = new ArgumentReference(parameters[i].ParameterType, i + 1); + } - var arguments = new ArgumentReference[parameters.Length]; + return arguments; + } - for (var i = 0; i < parameters.Length; ++i) + public Type[] GetTypes() { - arguments[i] = new ArgumentReference(parameters[i].ParameterType, i + 1); + if (parameters.Length == 0) return []; + + var types = new Type[parameters.Length]; + for (var i = 0; i < parameters.Length; i++) + { + types[i] = parameters[i].ParameterType; + } + return types; } - - return arguments; } - public static Type[] GetTypes(ParameterInfo[] parameters) + extension(Type[] parameterTypes) { - if (parameters.Length == 0) return []; - - var types = new Type[parameters.Length]; - for (var i = 0; i < parameters.Length; i++) + public ArgumentReference[] ConvertToArgumentReferences() { - types[i] = parameters[i].ParameterType; + if (parameterTypes.Length == 0) return []; + + var arguments = new ArgumentReference[parameterTypes.Length]; + + for (var i = 0; i < parameterTypes.Length; ++i) + { + arguments[i] = new ArgumentReference(parameterTypes[i], i + 1); + } + + return arguments; } - return types; } public static Type[] InitializeAndConvert(ArgumentReference[] arguments) diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.cs index b52bee5c1..f48d1e27c 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/MethodEmitter.cs @@ -64,12 +64,12 @@ internal MethodEmitter(ClassEmitter owner, string name, var returnType = methodToUseAsATemplate.ReturnType; var baseMethodParameters = methodToUseAsATemplate.GetParameters(); - var parameters = ArgumentsUtil.GetTypes(baseMethodParameters); + var parameterTypes = baseMethodParameters.GetTypes(); genericTypeParams = GenericUtil.CopyGenericArguments(methodToUseAsATemplate, builder); - SetParameters(parameters); + SetParameters(parameterTypes); SetReturnType(returnType); - SetSignature(returnType, methodToUseAsATemplate.ReturnParameter, parameters, baseMethodParameters); + SetSignature(returnType, methodToUseAsATemplate.ReturnParameter, parameterTypes, baseMethodParameters); DefineParameters(baseMethodParameters); } @@ -117,7 +117,7 @@ public void SetParameters(Type[] paramTypes) builder.SetParameters(paramTypes); Debug.Assert(MethodBuilder.IsStatic == false, "The following call to `ConvertToArgumentReferences` assumes the presence of a `this` parameter when assigning parameter positions."); - arguments = ArgumentsUtil.ConvertToArgumentReferences(paramTypes); + arguments = paramTypes.ConvertToArgumentReferences(); } public virtual void Generate() diff --git a/src/Castle.Core/DynamicProxy/Generators/ForwardingMethodGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/ForwardingMethodGenerator.cs index 52ef7736a..fe2e0bde1 100644 --- a/src/Castle.Core/DynamicProxy/Generators/ForwardingMethodGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/ForwardingMethodGenerator.cs @@ -33,7 +33,7 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C INamingScope namingScope) { var target = getTarget(@class, MethodToOverride); - var arguments = ArgumentsUtil.ConvertToArgumentReferences(MethodToOverride.GetParameters()); + var arguments = MethodToOverride.GetParameters().ConvertToArgumentReferences(); emitter.CodeBuilder.AddStatement(new ReturnStatement( new MethodInvocationExpression( diff --git a/src/Castle.Core/DynamicProxy/Generators/OptionallyForwardingMethodGenerator.cs b/src/Castle.Core/DynamicProxy/Generators/OptionallyForwardingMethodGenerator.cs index 4ebc688ab..b1dedf36e 100644 --- a/src/Castle.Core/DynamicProxy/Generators/OptionallyForwardingMethodGenerator.cs +++ b/src/Castle.Core/DynamicProxy/Generators/OptionallyForwardingMethodGenerator.cs @@ -50,7 +50,7 @@ protected override MethodEmitter BuildProxiedMethodBody(MethodEmitter emitter, C private IStatement IfNotNull(IExpression target) { var statements = new BlockStatement(); - var arguments = ArgumentsUtil.ConvertToArgumentReferences(MethodToOverride.GetParameters()); + var arguments = MethodToOverride.GetParameters().ConvertToArgumentReferences(); statements.AddStatement(new ReturnStatement( new MethodInvocationExpression( From 1ce2296f6a9cd6854c61a87349682f814aae1ee2 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Fri, 23 Jan 2026 12:31:21 +0100 Subject: [PATCH 8/8] Bring in `ParameterInfo` helpers from `GeneratorUtil` While we're at it, add some unit tests for them. --- .../ArgumentsUtilTestCase.cs | 64 ++++++++++++++++++ .../Generators/Emitters/ArgumentsUtil.cs | 65 ++++++++++++++++++- .../DynamicProxy/Generators/GeneratorUtil.cs | 65 +------------------ 3 files changed, 130 insertions(+), 64 deletions(-) create mode 100644 src/Castle.Core.Tests/DynamicProxy.Tests/ArgumentsUtilTestCase.cs diff --git a/src/Castle.Core.Tests/DynamicProxy.Tests/ArgumentsUtilTestCase.cs b/src/Castle.Core.Tests/DynamicProxy.Tests/ArgumentsUtilTestCase.cs new file mode 100644 index 000000000..129266cea --- /dev/null +++ b/src/Castle.Core.Tests/DynamicProxy.Tests/ArgumentsUtilTestCase.cs @@ -0,0 +1,64 @@ +// 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. + +namespace Castle.DynamicProxy.Tests +{ + using System.Linq; + using System.Reflection; + + using Castle.DynamicProxy.Generators.Emitters; + using Castle.DynamicProxy.Internal; + + using NUnit.Framework; + + [TestFixture] + public class ArgumentsUtilTestCase + { + public interface Methods + { + public void ByValue(int arg); + public void In(in int arg); + public void Out(out int arg); + public void Ref(ref int arg); + public void RefReadonly(ref readonly int arg); + } + + [TestCase(nameof(Methods.ByValue), false)] + [TestCase(nameof(Methods.In), true)] + [TestCase(nameof(Methods.Out), true)] + [TestCase(nameof(Methods.Ref), true)] + [TestCase(nameof(Methods.RefReadonly), true)] + public void IsByRef(string methodName, bool expected) + { + var parameter = GetParameterOf(methodName); + Assert.AreEqual(expected, parameter.IsByRef); + } + + [TestCase(nameof(Methods.In), true)] + [TestCase(nameof(Methods.Out), false)] + [TestCase(nameof(Methods.Ref), false)] + [TestCase(nameof(Methods.RefReadonly), true)] + public void IsReadOnly(string methodName, bool expected) + { + var parameter = GetParameterOf(methodName); + Assume.That(parameter.ParameterType.IsByRef); + Assert.AreEqual(expected, parameter.IsReadOnly); + } + + private ParameterInfo GetParameterOf(string methodName) + { + return typeof(Methods).GetMethod(methodName)!.GetParameters().Single(); + } + } +} diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs index 461f46db2..846c9de1d 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/ArgumentsUtil.cs @@ -15,13 +15,76 @@ namespace Castle.DynamicProxy.Generators.Emitters { using System; + using System.Linq; using System.Reflection; - using System.Reflection.Emit; + using System.Runtime.InteropServices; using Castle.DynamicProxy.Generators.Emitters.SimpleAST; internal static class ArgumentsUtil { + extension(ParameterInfo parameter) + { + public bool IsByRef + { + get + { + return parameter.ParameterType.IsByRef; + } + } + + public bool IsReadOnly + { + get + { + // C# `in` parameters are also by-ref, but meant to be read-only. + // The section "Metadata representation of in parameters" on the following page + // defines how such parameters are marked: + // + // https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/readonly-ref.md + // + // This poses three problems for detecting them: + // + // * The C# Roslyn compiler marks `in` parameters with an `[in]` IL modifier, + // but this isn't specified, nor is it used uniquely for `in` params. + // + // * `System.Runtime.CompilerServices.IsReadOnlyAttribute` is not defined on all + // .NET platforms, so the compiler sometimes recreates that type in the same + // assembly that contains the method having an `in` parameter. In other words, + // it's an attribute one must check for by name (which is slow, as it implies + // use of a `GetCustomAttributes` enumeration instead of a faster `IsDefined`). + // + // * A required custom modifier `System.Runtime.InteropServices.InAttribute` + // is always present in those cases relevant for DynamicProxy (proxyable methods), + // but not all targeted platforms support reading custom modifiers. Also, + // support for cmods is generally flaky (at this time of writing, mid-2018). + // + // The above points inform the following detection logic: + + // First, fast-guard against non-`in` params by checking for an IL `[in]` modifier: + if ((parameter.Attributes & (ParameterAttributes.In | ParameterAttributes.Out)) != ParameterAttributes.In) + { + return false; + } + + // Second, check for the required modifiers (hoping for good modreq support): + if (parameter.GetRequiredCustomModifiers().Any(x => x == typeof(InAttribute))) + { + return true; + } + + // Third, check for `IsReadOnlyAttribute` by name (see explanation above). + // This check is likely the slowest (despite being accurate) so we do it last: + if (parameter.GetCustomAttributes(false).Any(x => x.GetType().FullName == "System.Runtime.CompilerServices.IsReadOnlyAttribute")) + { + return true; + } + + return false; + } + } + } + extension(ParameterInfo[] parameters) { public ArgumentReference[] ConvertToArgumentReferences() diff --git a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs b/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs index 9ece9992b..a9847d664 100644 --- a/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.cs +++ b/src/Castle.Core/DynamicProxy/Generators/GeneratorUtil.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. @@ -14,9 +14,7 @@ namespace Castle.DynamicProxy.Generators { - using System.Linq; using System.Reflection; - using System.Runtime.InteropServices; using Castle.DynamicProxy.Generators.Emitters; using Castle.DynamicProxy.Generators.Emitters.SimpleAST; @@ -35,7 +33,7 @@ public static void CopyOutAndRefParameters(Reference[] dereferencedArguments, Lo for (var i = 0; i < parameters.Length; i++) { - if (IsByRef(parameters[i]) && !IsReadOnly(parameters[i])) + if (parameters[i].IsByRef && !parameters[i].IsReadOnly) { if (arguments == null) { @@ -70,65 +68,6 @@ public static void CopyOutAndRefParameters(Reference[] dereferencedArguments, Lo } } } - - bool IsByRef(ParameterInfo parameter) - { - return parameter.ParameterType.IsByRef; - } - - bool IsReadOnly(ParameterInfo parameter) - { - // C# `in` parameters are also by-ref, but meant to be read-only. - // The section "Metadata representation of in parameters" on the following page - // defines how such parameters are marked: - // - // https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/readonly-ref.md - // - // This poses three problems for detecting them: - // - // * The C# Roslyn compiler marks `in` parameters with an `[in]` IL modifier, - // but this isn't specified, nor is it used uniquely for `in` params. - // - // * `System.Runtime.CompilerServices.IsReadOnlyAttribute` is not defined on all - // .NET platforms, so the compiler sometimes recreates that type in the same - // assembly that contains the method having an `in` parameter. In other words, - // it's an attribute one must check for by name (which is slow, as it implies - // use of a `GetCustomAttributes` enumeration instead of a faster `IsDefined`). - // - // * A required custom modifier `System.Runtime.InteropServices.InAttribute` - // is always present in those cases relevant for DynamicProxy (proxyable methods), - // but not all targeted platforms support reading custom modifiers. Also, - // support for cmods is generally flaky (at this time of writing, mid-2018). - // - // The above points inform the following detection logic: First, we rely on an IL - // `[in]` modifier being present. This is a "fast guard" against non-`in` parameters: - if ((parameter.Attributes & (ParameterAttributes.In | ParameterAttributes.Out)) != ParameterAttributes.In) - { - return false; - } - - // This check allows to make the detection logic more robust on the platforms which support custom modifiers. - // The robustness is achieved by the fact, that usually the `IsReadOnlyAttribute` emitted by the compiler is internal to the assembly. - // Therefore, if clients use Reflection.Emit to create "a copy" of the methods with read-only members, they cannot re-use the existing attribute. - // Instead, they are forced to emit their own `IsReadOnlyAttribute` to mark some argument as immutable. - // The `InAttribute` type OTOH was always available in BCL. Therefore, it's much easier to copy the modreq and be recognized by Castle. - // - // If check fails, resort to the IsReadOnlyAttribute check. - // Check for the required modifiers first, as it's faster. - if (parameter.GetRequiredCustomModifiers().Any(x => x == typeof(InAttribute))) - { - return true; - } - - // The comparison by name is intentional; any assembly could define that attribute. - // See explanation in comment above. - if (parameter.GetCustomAttributes(false).Any(x => x.GetType().FullName == "System.Runtime.CompilerServices.IsReadOnlyAttribute")) - { - return true; - } - - return false; - } } private static ConvertExpression Argument(int i, LocalReference invocationArgs, Reference[] arguments)