dotnetcore / EasyCaching

:boom: EasyCaching is an open source caching library that contains basic usages and some advanced usages of caching which can help us to handle caching more easier!
MIT License
1.92k stars 324 forks source link

System.MissingMethodException when using EasyCaching.Serialization.MemoryPack with .NET 7.0 and above #537

Open mjebrahimi opened 1 month ago

mjebrahimi commented 1 month ago

Description

The exception below is thrown when using EasyCaching.Serialization.MemoryPack with .NET 7.0 and above. This exception is not thrown when using .NET 6.0

System.MissingMethodException: Method not found: 'Void MemoryPack.MemoryPackSerializerOptions.set_StringEncoding(MemoryPack.StringEncoding)'.

Steps to Reproduce

  1. Clone repository at https://github.com/mjebrahimi/EasyCachingMemoryPack
  2. Run

Code

var services = new ServiceCollection();

services.AddEasyCaching(options => options.WithMemoryPack());

var provider = services.BuildServiceProvider();

var serializer = provider.GetRequiredService<IEasyCachingSerializer>();
//Throws => System.MissingMethodException: Method not found: 'Void MemoryPack.MemoryPackSerializerOptions.set_StringEncoding(MemoryPack.StringEncoding)'.

Specifications

Root Cause

It's a very rare incompatibility issue and the root cause is a bit complicated!

MemoryPack v1.9.7 is built on .NETStandard 2.1 and .NET7.0 Because of using init properties, it includes the code below If the TargetFramework is not .NET5.0 or above (like .NETStandard2.1)

#if !NET5_0_OR_GREATER

namespace System.Runtime.CompilerServices
{
    internal sealed class IsExternalInit
    {
    }
}

#endif

On the other side EasyCaching.Serialization.MemoryPack v1.9.2 is built on .NET 6.0 so it uses (rollbacks to) the .NETStandard2.1 compiled of the MemoryPack.

Other the other side my project is using .NET7.0 so in hierarchy of dependencies it uses the .NET7.0 compiled of the MemoryPack which does not contain System.Runtime.CompilerServices.IsExternalInit code.

Therefor the difference between referencing to two different compiled package of MemoryPack leads to this exception.

The right solution is to support .NET 7.0 target-framework in EasyCaching.Serialization.MemoryPack.

But we can also solve this problem by using reflection code below instead of this in EasyCaching.Serialization.MemoryPack.

var options = MemoryPackSerializerOptions.Default;
typeof(MemoryPackSerializerOptions)
    .GetProperty(nameof(MemoryPackSerializerOptions.StringEncoding))
    .SetValue(options, easyCachingOptions.StringEncoding);
jodydonetti commented 1 month ago

Welcome to the club @mjebrahimi : long story short, it's a problem related to transitive dependencies and source-generated code, and that is the intended design apparently.

See https://github.com/Cysharp/MemoryPack/issues/96

Solution: all the intermediate packages MUST multi-target, that is the only way around it.

Hope this helps.

mjebrahimi commented 1 month ago

Thanks, @jodydonetti

No bro, it's not the only way :) Using reflection can solve this problem easily. (it's worth a try :)

And even your solution is not applicable. We can't force other lib authors of all intermediate packages to support our desired target frameworks!

To make it short, there are 4 solutions: 1- EasyCaching must support .NET 7.0 2- Using reflection as a workaround 3- MemoryPack must support .NET 6.0 4- MemoryPack must include System.Runtime.CompilerServices.IsExternalInit in .NET 5.0 and above

A question BTW, What do you mean by source-generated code? We don't have any source-generated code here, neither in EasyCaching libs nor MemoryPack related to this case.

Bro, this problem has nothing to do with source-generated code, it's just because of the lack of System.Runtime.CompilerServices.IsExternalInit in the compiled package for two different target frameworks.

I have demonstrated all these in https://github.com/mjebrahimi/EasyCachingMemoryPack (give it a try ;)

I read your issues for FusionCahce and MemoryPack, it's a different problem, not the same.

jodydonetti commented 1 month ago

Maybe this problem is very similar to, but in the end actually different from, that problem I had back then: at first it looked very similar, but I did not have time to look into all the details, so I linked my old issue hoping it could've been of help.

And even your solution is not applicable. We can't force other lib authors of all intermediate packages to support our desired target frameworks!

I know, I don't like it either, and that is why back then I asked the author for a different apporach, but alas.

A question BTW, What do you mean by source-generated code? We don't have any source-generated code here, neither in EasyCaching libs nor MemoryPack related to this case.

MemoryPack is heavily designed around source generators and uses them a lot behind the covers, and at least in my case that was the main issue (related to transient TFMs dependencies and the code being generated).

If that is in fact not the same thing here, sorry for having wasted your time.

mjebrahimi commented 1 month ago

No worries, thanks for participating :) it was my pleasure to know your experience. I learned another problem thanks to you :)