Closed
Description
Similar to what occurred in #601, when trying to mock (with Moq) records derived from a base generic record, an exception is thrown.
Repro code:
var mock1 = new Mock<MyDerivedRecord>();
var mock2 = new Mock<MyDerivedGenericRecord>();
var a = mock1.Object;
var b = mock2.Object;
public abstract record MyBaseRecord
{
}
public abstract record MyBaseGenericRecord<T>
{
public T Prop { get; set; }
}
public record MyDerivedRecord : MyBaseRecord
{
}
public record MyDerivedGenericRecord : MyBaseGenericRecord<int>
{
}
Variable a
holds the mocked object as expected, but when initializing b
, the following is thrown:
System.ArgumentException
HResult=0x80070057
Message=Type to mock (MyDerivedGenericRecord) must be an interface, a delegate, or a non-sealed, non-static class.
Source=Moq
StackTrace:
at Moq.CastleProxyFactory.CreateProxy(Type mockType, IInterceptor interceptor, Type[] interfaces, Object[] arguments) in C:\projects\moq4\src\Moq\Interception\CastleProxyFactory.cs:line 66
at Moq.Mock`1.InitializeInstance() in C:\projects\moq4\src\Moq\Mock`1.cs:line 307
at Moq.Mock`1.OnGetObject() in C:\projects\moq4\src\Moq\Mock`1.cs:line 326
at Moq.Mock`1.get_Object() in C:\projects\moq4\src\Moq\Mock`1.cs:line 281
at Program.<Main>$(String[] args) in C:\Users\Cesar\source\repos\ConsoleApp1\Program.cs:line 11
This exception was originally thrown at this call stack:
System.Reflection.Emit.TypeBuilder.CreateTypeNoLock()
System.Reflection.Emit.TypeBuilder.CreateTypeInfo()
Castle.DynamicProxy.Generators.Emitters.AbstractTypeEmitter.BuildType()
Castle.DynamicProxy.Generators.BaseClassProxyGenerator.GenerateType(string, Castle.DynamicProxy.Generators.INamingScope)
Castle.Core.Internal.SynchronizedDictionary<TKey, TValue>.GetOrAdd(TKey, System.Func<TKey, TValue>)
Castle.DynamicProxy.Generators.BaseProxyGenerator.GetProxyType()
Castle.DynamicProxy.ProxyGenerator.CreateClassProxy(System.Type, System.Type[], Castle.DynamicProxy.ProxyGenerationOptions, object[], Castle.DynamicProxy.IInterceptor[])
Moq.CastleProxyFactory.CreateProxy(System.Type, Moq.IInterceptor, System.Type[], object[]) in CastleProxyFactory.cs
Inner Exception 1:
TypeLoadException: Return type in method 'Castle.Proxies.MyDerivedGenericRecordProxy_2.<Clone>$()' on type 'Castle.Proxies.MyDerivedGenericRecordProxy_2' from assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is not compatible with base type method 'MyDerivedGenericRecord.<Clone>$()'.
Originally posted by @CesarD in #601 (comment)
Activity
[-]Mocking Records derived from a base generic record broken using .NET 6 compiler[/-][+]Proxies using Records derived from a base generic record broken using .NET 6 compiler[/+]CesarD commentedon Aug 27, 2022
@stakx not sure if you were aware of this one.
stakx commentedon Dec 11, 2022
@CesarD, sorry for not getting back to you sooner. I'll try looking into this in the next few days.
CesarD commentedon Dec 11, 2022
Thanks so much!! 🤩🤩
stakx commentedon Dec 28, 2022
Finally found some time to look into this today. As suspected, the root cause for this issue is the same as in #601 (comment). We solved that other issue by adding the following logic inside
MethodSignatureComparer.EqualSignatureTypes
:Core/src/Castle.Core/DynamicProxy/Generators/MethodSignatureComparer.cs
Lines 132 to 139 in 323c03f
But before code execution gets there, it has to go through several other checks, amongst them this one here:
Core/src/Castle.Core/DynamicProxy/Generators/MethodSignatureComparer.cs
Lines 93 to 96 in 323c03f
This is why your scenario with a generic record base class fails: DynamicProxy compares the signatures of the
<Clone>$
methods fromMyBaseGenericRecord<T>
andMyDerivedGenericRecord
; and because one of those types is generic while the other is not, the above condition is met and we never get to the check for covariant returns. DynamicProxy then thinks that there are two distinct<Clone>$
methods and tries to implement them both... which is what causes Reflection Emit to throw.CesarD commentedon Dec 29, 2022
Thanks a lot for taking care of this one!! <3
stakx commentedon Dec 30, 2022
@CesarD, FYI, I've just pushed Moq 4.18.4 to NuGet, which now uses Castle.Core 5.1.1, which includes a fix for your issue.
CesarD commentedon Dec 30, 2022
Thank you so much!!! 👏🏼🫶🏼