-
Notifications
You must be signed in to change notification settings - Fork 485
"Duplicate element: MetaMethod" for specific combinations of namespace & type names #469
Description
Originally reported at nsubstitute/NSubstitute#585, this looks like a bug in DynamicProxy.
Code to reproduce:
using Castle.DynamicProxy;
namespace B
{
public interface I
{
void M();
}
}
namespace A
{
public interface I
{
void M();
}
public interface H : I
{
new void M();
}
}
class Program
{
static void Main()
{
var generator = new ProxyGenerator();
generator.CreateClassProxy(
classToProxy: typeof(object),
additionalInterfacesToProxy: new[] { typeof(B.I), typeof(A.H) },
interceptors: new StandardInterceptor());
}
}The above program triggers the following exception:
Castle.DynamicProxy.ProxyGenerationException: Duplicate element: Castle.DynamicProxy.Generators.MetaMethod at Castle.DynamicProxy.Generators.TypeElementCollection`1.Add(TElement item) in ...\src\Castle.Core\DynamicProxy\Generators\TypeElementCollection.cs:line 51 at Castle.DynamicProxy.Generators.MetaType.AddMethod(MetaMethod method) in ...\src\Castle.Core\DynamicProxy\Generators\MetaType.cs:line 48 at Castle.DynamicProxy.Contributors.CompositeTypeContributor.CollectElementsToProxy(IProxyGenerationHook hook, MetaType model) in ...\src\Castle.Core\DynamicProxy\Contributors\CompositeTypeContributor.cs:line 55 at Castle.DynamicProxy.Generators.ClassProxyGenerator.GenerateType(String name, Type[] interfaces, INamingScope namingScope) in ...\src\Castle.Core\DynamicProxy\Generators\ClassProxyGenerator.cs:line 57 at Castle.DynamicProxy.Generators.ClassProxyGenerator.<>c__DisplayClass1_0.<GenerateCode>b__0(String n, INamingScope s) in ...\src\Castle.Core\DynamicProxy\Generators\ClassProxyGenerator.cs:line 45 at Castle.DynamicProxy.Generators.BaseProxyGenerator.<>c__DisplayClass33_0.<ObtainProxyType>b__0(CacheKey _) in ...\src\Castle.Core\DynamicProxy\Generators\BaseProxyGenerator.cs:line 401 at Castle.Core.Internal.SynchronizedDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory) in ...\src\Castle.Core\Core\Internal\SynchronizedDictionary.cs:line 74 at Castle.DynamicProxy.Generators.BaseProxyGenerator.ObtainProxyType(CacheKey cacheKey, Func`3 factory) in ...\src\Castle.Core\DynamicProxy\Generators\BaseProxyGenerator.cs:line 393 at Castle.DynamicProxy.Generators.ClassProxyGenerator.GenerateCode(Type[] interfaces, ProxyGenerationOptions options) in ...\src\Castle.Core\DynamicProxy\Generators\ClassProxyGenerator.cs:line 45 at Castle.DynamicProxy.DefaultProxyBuilder.CreateClassProxyType(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options) in ...\src\Castle.Core\DynamicProxy\DefaultProxyBuilder.cs:line 68 at Castle.DynamicProxy.ProxyGenerator.CreateClassProxyType(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options) in ...\src\Castle.Core\DynamicProxy\ProxyGenerator.cs:line 1538 at Castle.DynamicProxy.ProxyGenerator.CreateClassProxy(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, Object[] constructorArguments, IInterceptor[] interceptors) in ...\src\Castle.Core\DynamicProxy\ProxyGenerator.cs:line 1440 at Castle.DynamicProxy.ProxyGenerator.CreateClassProxy(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, IInterceptor[] interceptors) in ...\src\Castle.Core\DynamicProxy\ProxyGenerator.cs:line 1392 at Castle.DynamicProxy.ProxyGenerator.CreateClassProxy(Type classToProxy, Type[] additionalInterfacesToProxy, IInterceptor[] interceptors) in ...\src\Castle.Core\DynamicProxy\ProxyGenerator.cs:line 1257 at Program.Main()
As noted in the original issue, namespace and type names matter a lot here. Swapping the namespace names, renaming the namespace A to a, or renaming the interface H to h, will make the problem go away.
Preliminary analysis
-
The reason why three identically-named types are needed is the same as in Prevent member name collision when proxy implements same generic interface more than twice #285:
- The first interface type's method
Mgets implicitly implemented asM. - The second interface type's method
Mwill get namedI.Mon the proxy type, i.e. DynamicProxy switches to explicit interface implementation to prevent a name collision. - For the third interface type's method
M, DynamicProxy tries the same strategy to prevent a name collision withMfrom step (1), which leads to a name collision withI.Mfrom step (2).
- The first interface type's method
-
The fact that small name differences like upper/lower case makes a difference points at the type sorting that DynamicProxy uses (IIRC to encourage cache hits).
I am not yet sure how these two things interact with one another to provoke failure.
My current guess is that the error will happen iff the M method from the interface that isn't named like the two others gets processed first; because that will mean the others will both have to be explicitly named as I.M.
If the types, on the other hand, get sorted such that an I.M method get processed first, there won't be a collision as the proxy type ends up with three methods M, I.M, and H.M.
/cc @compact-github, @zvirja