devlooped / moq

The most popular and friendly mocking framework for .NET
Other
5.94k stars 802 forks source link

`System.TypeLoadException : Could not load type 'Castle.Core.Internal.CollectionExtensions' from assembly 'Castle.Core, Version=5.0.0.0` after updating to Moq 4.18.4 #1320

Closed Saibamen closed 1 year ago

Saibamen commented 1 year ago

System.TypeLoadException : Could not load type 'Castle.Core.Internal.CollectionExtensions' from assembly 'Castle.Core, Version=5.0.0.0 after updating from Moq 4.18.3 to Moq 4.18.4

stakx commented 1 year ago

Can you provide a minimal but complete project that reproduces the exception you're reporting? Otherwise there's nothing we can do here.

stakx commented 1 year ago

Actually, never mind. This cannot be caused by Moq. My guess is that you have some other dependency installed that relies on Castle.Core v4 (possibly Windsor), and your updating Moq now forces v5 of that library. I suggest you either update those other dependencies, or you downgrade Moq.

Saibamen commented 1 year ago

Sorry, it has nothing to do with Moq

In my EFCore project, I used IsNullOrEmpty extension for dictionary from Castle.Core.Internal namespace (https://github.com/castleproject/Core/blob/v4.4.1/src/Castle.Core/Core/Internal/CollectionExtensions.cs). I changed it to Microsoft.IdentityModel.Tokens namespace

MelGrubb commented 1 year ago

It looks like, even though we don't have a direct reference to Castle.Core.Internal in our own project, ReSharper picked up on the transitive reference and "helped" add the reference to enable a call to IsNullOrEmpty at some point in our history.

The really bizarre thing is that the code breaks at the point before it calls the method with the IsNullOrEmpty in it. It's somehow psychic. We have no problem compiling the code, but at runtime it breaks when you mention the code with the IsNullOrEmpty call in it. It never even enters the method itself. I put a breakpoint in the method before it calls IsNullOrEmpty, and the method is never even entered. It's spooky. Replacing the IsNullOrEmpty call with Foos?.Any() == true accomplishes the same thing, but eliminates the error.

This is some voodoo here. I'm going to just walk away.

stakx commented 1 year ago

@MelGrubb, I'm guessing that this could be just-in-time (JIT) compilation at work. The following explanation may be somewhat inaccurate but maybe it aids your understanding nevertheless: .NET compiles methods to assembly code the first time it encounters them. That is, say you have a method M that calls method X, which during the execution of your program has never been called before. When it encounters a call instruction in method M for method X, it needs to compile X before X can actually run. And if X's bytecode contains a reference to any type or type member that cannot be resolved (say, the IsNullOrEmpty thingy you mention... e.g. due to the referenced assembly missing), then compilation of X cannot complete and X cannot be called at all, so your breakpoint will never be hit because execution gets stuck in M.

MelGrubb commented 1 year ago

Since the class that the problem occurs in has quite definitely been compiled by this point, and has been used throughout the test, I would think it had been compiled already. I was unaware that it would compile it piecemeal like that. Still, if Castle.Core hid the IsNullOrEmpty method between v4 and v5, I ought to be getting a compilation error, shouldn't I? If that method isn't visible anymore, then Intellisense shouldn't be suggesting it.

The weird part is that the error would happen in the code calling my method and not in the method itself. It does smell like some kind of JIT issue, and my only guess at this point is that somehow I have a v4 Castle.Core "visible" when writing the code that gets replaced by a v5 at runtime. It's the only explanation that makes sense to me.

Anyway, I've just dropped in my own IsNullOrEmpty extension in, and everything is back to normal again. Thanks for the follow-up.

justmeeee commented 1 year ago

Had the same issue. Interestingly IsNullOrEmpty of type IEnumerable was used on a string, which shouldn't be possible at all?