diff --git a/CHANGELOG.md b/CHANGELOG.md index cf3cb2227..c9b43bdbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Enhancements: Bugfixes: - `InvalidProgramException` when proxying `MemoryStream` with .NET 7 (@stakx, #651) - `invocation.MethodInvocationTarget` throws `ArgumentNullException` for default interface method (@stakx, #684) +- `DynamicProxyException` ("duplicate element") when type to proxy contains members whose names differ only in case (@stakx, #691) Deprecations: - .NET Core 2.1, .NET Core 3.1, .NET 6, and mono tests diff --git a/src/Castle.Core.Tests/DynamicProxy.Tests/CaseSensitivityTestCase.cs b/src/Castle.Core.Tests/DynamicProxy.Tests/CaseSensitivityTestCase.cs new file mode 100644 index 000000000..fa630505b --- /dev/null +++ b/src/Castle.Core.Tests/DynamicProxy.Tests/CaseSensitivityTestCase.cs @@ -0,0 +1,103 @@ +// Copyright 2004-2025 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; + + using Castle.DynamicProxy.Tests.Interceptors; + + using NUnit.Framework; + + [TestFixture] + public class CaseSensitivityTestCase : BasePEVerifyTestCase + { + [TestCase(typeof(IDifferentlyCasedEvents))] + [TestCase(typeof(IDifferentlyCasedMethods))] + [TestCase(typeof(IDifferentlyCasedProperties))] + public void Can_proxy_type_with_differently_cased_members(Type interfaceTypeToProxy) + { + _ = generator.CreateInterfaceProxyWithoutTarget(interfaceTypeToProxy); + } + + [Test] + public void Can_distinguish_differently_cased_events_during_interception() + { + var interceptor = new KeepDataInterceptor(); + var proxy = generator.CreateInterfaceProxyWithoutTarget(interceptor); + + proxy.Abc += delegate { }; + Assert.AreEqual("add_Abc", interceptor.Invocation.Method.Name); + + proxy.aBc += delegate { }; + Assert.AreEqual("add_aBc", interceptor.Invocation.Method.Name); + + proxy.abC += delegate { }; + Assert.AreEqual("add_abC", interceptor.Invocation.Method.Name); + } + + [Test] + public void Can_distinguish_differently_cased_methods_during_interception() + { + var interceptor = new KeepDataInterceptor(); + var proxy = generator.CreateInterfaceProxyWithoutTarget(interceptor); + + proxy.Abc(); + Assert.AreEqual("Abc", interceptor.Invocation.Method.Name); + + proxy.aBc(); + Assert.AreEqual("aBc", interceptor.Invocation.Method.Name); + + proxy.abC(); + Assert.AreEqual("abC", interceptor.Invocation.Method.Name); + } + + [Test] + public void Can_distinguish_differently_cased_properties_during_interception() + { + var interceptor = new KeepDataInterceptor(); + var proxy = generator.CreateInterfaceProxyWithoutTarget(interceptor); + + _ = proxy.Abc; + Assert.AreEqual("get_Abc", interceptor.Invocation.Method.Name); + + _ = proxy.aBc; + Assert.AreEqual("get_aBc", interceptor.Invocation.Method.Name); + + _ = proxy.abC; + Assert.AreEqual("get_abC", interceptor.Invocation.Method.Name); + } + + public interface IDifferentlyCasedEvents + { + event EventHandler Abc; + event EventHandler aBc; + event EventHandler abC; + } + + public interface IDifferentlyCasedMethods + { + void Abc(); + void aBc(); + void abC(); + } + + public interface IDifferentlyCasedProperties + { + object Abc { get; } + object aBc { get; } + object abC { get; } + } + } +} diff --git a/src/Castle.Core/DynamicProxy/Generators/Emitters/ClassEmitter.cs b/src/Castle.Core/DynamicProxy/Generators/Emitters/ClassEmitter.cs index 151c7ff26..c4b4ba706 100644 --- a/src/Castle.Core/DynamicProxy/Generators/Emitters/ClassEmitter.cs +++ b/src/Castle.Core/DynamicProxy/Generators/Emitters/ClassEmitter.cs @@ -34,7 +34,7 @@ internal sealed class ClassEmitter private readonly List events; private readonly IDictionary fields = - new Dictionary(StringComparer.OrdinalIgnoreCase); + new Dictionary(StringComparer.Ordinal); private readonly List methods; diff --git a/src/Castle.Core/DynamicProxy/Generators/MetaEvent.cs b/src/Castle.Core/DynamicProxy/Generators/MetaEvent.cs index a326d4fc7..4142ef2a4 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MetaEvent.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MetaEvent.cs @@ -1,4 +1,4 @@ -// Copyright 2004-2021 Castle Project - http://www.castleproject.org/ +// Copyright 2004-2025 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. @@ -128,7 +128,7 @@ public bool Equals(MetaEvent other) return true; } - if (!StringComparer.OrdinalIgnoreCase.Equals(Name, other.Name)) + if (!StringComparer.Ordinal.Equals(Name, other.Name)) { return false; } diff --git a/src/Castle.Core/DynamicProxy/Generators/MetaMethod.cs b/src/Castle.Core/DynamicProxy/Generators/MetaMethod.cs index 91b66bf39..d3b95e9eb 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MetaMethod.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MetaMethod.cs @@ -1,4 +1,4 @@ -// Copyright 2004-2021 Castle Project - http://www.castleproject.org/ +// Copyright 2004-2025 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. @@ -61,7 +61,7 @@ public bool Equals(MetaMethod other) return true; } - if (!StringComparer.OrdinalIgnoreCase.Equals(Name, other.Name)) + if (!StringComparer.Ordinal.Equals(Name, other.Name)) { return false; } diff --git a/src/Castle.Core/DynamicProxy/Generators/MetaProperty.cs b/src/Castle.Core/DynamicProxy/Generators/MetaProperty.cs index b2b66066c..c99c52798 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MetaProperty.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MetaProperty.cs @@ -1,4 +1,4 @@ -// Copyright 2004-2021 Castle Project - http://www.castleproject.org/ +// Copyright 2004-2025 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. @@ -159,7 +159,7 @@ public bool Equals(MetaProperty other) return true; } - if (!StringComparer.OrdinalIgnoreCase.Equals(Name, other.Name)) + if (!StringComparer.Ordinal.Equals(Name, other.Name)) { return false; } diff --git a/src/Castle.Core/DynamicProxy/Internal/TypeUtil.cs b/src/Castle.Core/DynamicProxy/Internal/TypeUtil.cs index 30159bf81..96e7fa95c 100644 --- a/src/Castle.Core/DynamicProxy/Internal/TypeUtil.cs +++ b/src/Castle.Core/DynamicProxy/Internal/TypeUtil.cs @@ -1,4 +1,4 @@ -// Copyright 2004-2021 Castle Project - http://www.castleproject.org/ +// Copyright 2004-2025 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. @@ -195,12 +195,12 @@ internal static bool IsFinalizer(this MethodInfo methodInfo) internal static bool IsGetType(this MethodInfo methodInfo) { - return methodInfo.DeclaringType == typeof(object) && string.Equals("GetType", methodInfo.Name, StringComparison.OrdinalIgnoreCase); + return methodInfo.DeclaringType == typeof(object) && string.Equals("GetType", methodInfo.Name, StringComparison.Ordinal); } internal static bool IsMemberwiseClone(this MethodInfo methodInfo) { - return methodInfo.DeclaringType == typeof(object) && string.Equals("MemberwiseClone", methodInfo.Name, StringComparison.OrdinalIgnoreCase); + return methodInfo.DeclaringType == typeof(object) && string.Equals("MemberwiseClone", methodInfo.Name, StringComparison.Ordinal); } internal static void SetStaticField(this Type type, string fieldName, BindingFlags additionalFlags, object? value) @@ -252,7 +252,7 @@ public static MemberInfo[] Sort(MemberInfo[] members) { var sortedMembers = new MemberInfo[members.Length]; Array.Copy(members, sortedMembers, members.Length); - Array.Sort(sortedMembers, (l, r) => string.Compare(l.Name, r.Name, StringComparison.OrdinalIgnoreCase)); + Array.Sort(sortedMembers, (l, r) => string.Compare(l.Name, r.Name, StringComparison.Ordinal)); return sortedMembers; }