From 638dfe64934f4f45a812e818aa9940a415c37005 Mon Sep 17 00:00:00 2001 From: Jelle Kerkstra Date: Thu, 14 Nov 2024 19:31:48 +0100 Subject: [PATCH 1/2] Include DeclaringType.Name for nested types when generating explicit implementation names --- .../Generators/MetaTypeElement.cs | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/Castle.Core/DynamicProxy/Generators/MetaTypeElement.cs b/src/Castle.Core/DynamicProxy/Generators/MetaTypeElement.cs index 12088d8ca7..8ffccb0331 100644 --- a/src/Castle.Core/DynamicProxy/Generators/MetaTypeElement.cs +++ b/src/Castle.Core/DynamicProxy/Generators/MetaTypeElement.cs @@ -62,23 +62,30 @@ protected void SwitchToExplicitImplementationName() nameBuilder.Append(ns); nameBuilder.Append('.'); } - AppendTypeName(nameBuilder, sourceType); + AppendTypeName(nameBuilder, sourceType, true); nameBuilder.Append('.'); nameBuilder.Append(name); this.name = nameBuilder.ToString(); } else if (ns != null) { - this.name = string.Concat(ns, ".", sourceType.Name, ".", name); + this.name = string.Concat(ns, ".", GetFullTypeName(sourceType), ".", name); } else { - this.name = string.Concat(sourceType.Name, ".", name); + this.name = string.Concat(GetFullTypeName(sourceType), ".", name); } - static void AppendTypeName(StringBuilder nameBuilder, Type type) + static void AppendTypeName(StringBuilder nameBuilder, Type type, bool fullName = false) { + if (fullName) + { + AppendFullTypeName(nameBuilder, type); + return; + } + nameBuilder.Append(type.Name); + if (type.IsGenericType) { nameBuilder.Append('['); @@ -94,6 +101,31 @@ static void AppendTypeName(StringBuilder nameBuilder, Type type) nameBuilder.Append(']'); } } + + static string GetFullTypeName(Type type) + { + var nameBuilder = new StringBuilder(); + AppendFullTypeName(nameBuilder, type); + return nameBuilder.ToString(); + } + + static void AppendFullTypeName(StringBuilder nameBuilder, Type type) + { + if (type.IsNested) + { + AppendFullTypeName(nameBuilder, type.DeclaringType); + nameBuilder.Append('.'); + } + + if (type.IsGenericType) + { + AppendTypeName(nameBuilder, type); + } + else + { + nameBuilder.Append(type.Name); + } + } } } } \ No newline at end of file From 8f59f678f8507cb15ee5e66fb0c9efa16743d51e Mon Sep 17 00:00:00 2001 From: Jelle Kerkstra Date: Thu, 14 Nov 2024 19:31:57 +0100 Subject: [PATCH 2/2] Add ExplicitlyImplementedNestedMethodNamesTestCase --- ...tlyImplementedNestedMethodNamesTestCase.cs | 71 +++++++++++++++++++ .../Interfaces/INestedSharedName.cs | 43 +++++++++++ 2 files changed, 114 insertions(+) create mode 100644 src/Castle.Core.Tests/DynamicProxy.Tests/ExplicitlyImplementedNestedMethodNamesTestCase.cs create mode 100644 src/Castle.Core.Tests/DynamicProxy.Tests/Interfaces/INestedSharedName.cs diff --git a/src/Castle.Core.Tests/DynamicProxy.Tests/ExplicitlyImplementedNestedMethodNamesTestCase.cs b/src/Castle.Core.Tests/DynamicProxy.Tests/ExplicitlyImplementedNestedMethodNamesTestCase.cs new file mode 100644 index 0000000000..f2261bb66b --- /dev/null +++ b/src/Castle.Core.Tests/DynamicProxy.Tests/ExplicitlyImplementedNestedMethodNamesTestCase.cs @@ -0,0 +1,71 @@ +// Copyright 2004-2021 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 System.Reflection; + + using NUnit.Framework; + + using INestedSharedNameFromA = Interfaces.OuterWrapper.InnerWrapperA.ISharedName; + using INestedSharedNameFromB = Interfaces.OuterWrapper.InnerWrapperB.ISharedName; + using INestedSharedNameFromC = Interfaces.OuterWrapper.InnerWrapperC.ISharedName; + + [TestFixture] + public class ExplicitlyImplementedNestedMethodNamesTestCase + { + [Test] + public void DynamicProxy_includes_namespace_and_declaring_type_and_type_name_in_names_of_explicitly_implemented_methods() + { + var a = typeof(INestedSharedNameFromA); + var b = typeof(INestedSharedNameFromB); + var c = typeof(INestedSharedNameFromC); + + var proxy = new ProxyGenerator().CreateInterfaceProxyWithoutTarget( + interfaceToProxy: a, + additionalInterfacesToProxy: new[] { b, c }, + interceptors: new StandardInterceptor()); + + var implementingType = proxy.GetType(); + + AssertNamingSchemeOfExplicitlyImplementedMethods(b, c, implementingType); + } + + private void AssertNamingSchemeOfExplicitlyImplementedMethods(Type b, Type c, Type implementingType) + { + const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + + // The assertions at the end of this method only make sense if certain preconditions + // are met. We verify those using NUnit assumptions: + + // We require two interface types that have the same name and a method named `M` each: + Assume.That(b.IsInterface); + Assume.That(c.IsInterface); + Assume.That(b.Name == c.Name); + Assume.That(b.GetMethod("M") != null); + Assume.That(c.GetMethod("M") != null); + + // We also need a type that implements the above interfaces: + Assume.That(b.IsAssignableFrom(implementingType)); + Assume.That(c.IsAssignableFrom(implementingType)); + + // If all of the above conditions are met, we expect the methods from the interfaces + // to be implemented explicitly. For our purposes, this means that they follow the + // naming scheme `...M`: + Assert.NotNull(implementingType.GetMethod($"{b.Namespace}.{b.DeclaringType.DeclaringType.Name}.{b.DeclaringType.Name}.{b.Name}.M", bindingFlags)); + Assert.NotNull(implementingType.GetMethod($"{c.Namespace}.{b.DeclaringType.DeclaringType.Name}.{b.DeclaringType.Name}.{c.Name}.M", bindingFlags)); + } + } +} diff --git a/src/Castle.Core.Tests/DynamicProxy.Tests/Interfaces/INestedSharedName.cs b/src/Castle.Core.Tests/DynamicProxy.Tests/Interfaces/INestedSharedName.cs new file mode 100644 index 0000000000..989ce1cb2c --- /dev/null +++ b/src/Castle.Core.Tests/DynamicProxy.Tests/Interfaces/INestedSharedName.cs @@ -0,0 +1,43 @@ +// Copyright 2004-2021 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.Interfaces +{ + public static class OuterWrapper + { + public static class InnerWrapperA + { + public interface ISharedName + { + void M(); + } + } + + public static class InnerWrapperB + { + public interface ISharedName + { + void M(); + } + } + + public static class InnerWrapperC + { + public interface ISharedName + { + void M(); + } + } + } +}