System.MissingMethodException: Method not found: 'Void System.Text.Json.Serialization.Metadata.JsonObjectInfoValues`1.set_ObjectCreator(System.Func`1<!0>)'. with .NET Standard 2.0
hugoqribeiro opened this issue · 13 comments
Description
I have a .NET 6 app that references a .NET Standard 2.0 class library, which in turn uses the new System.Text.Json source generator to speed up serialization.
When running I get the following exception (this stack trace is from a project used to reproduce the error):
System.MissingMethodException
HResult=0x80131513
Message=Method not found: 'Void System.Text.Json.Serialization.Metadata.JsonObjectInfoValues`1.set_ObjectCreator(System.Func`1<!0>)'.
Source=ClassLibrary1
StackTrace:
at ClassLibrary1.SerializerContext.get_MyModel() in D:\PrjNET\_Experiments\SystemTextJsonSerializerContext\ClassLibrary1\System.Text.Json.SourceGeneration\System.Text.Json.SourceGeneration.JsonSourceGenerator\SerializerContext.MyModel.g.cs:line 37
at ClassLibrary1.Serializer.Serialize(MyModel model) in D:\PrjNET\_Experiments\SystemTextJsonSerializerContext\ClassLibrary1\Serializer.cs:line 9
at Program.<Main>$(String[] args) in D:\PrjNET\_Experiments\SystemTextJsonSerializerContext\ConsoleApp1\Program.cs:line 5
This DOES NOT happen if the class library targets .NET 6.
Reproduction Steps
I've created a sample to reproduce this so I could eliminate the possibility of something being wrong with the original projects (upgraded from .NET 5).
The class library (netstandard2.0) code:
namespace ClassLibrary1
{
public class MyModel
{
public string Value
{
get; set;
}
}
public partial class Serializer
{
public static string Serialize(MyModel model)
{
return JsonSerializer.Serialize(
model,
SerializerContext.Default.MyModel);
}
public static string SerializeWithGenerics<T>(T model)
{
return JsonSerializer.Serialize(
model,
typeof(T),
SerializerContext.Default);
}
}
[JsonSerializable(typeof(MyModel))]
internal partial class SerializerContext : JsonSerializerContext
{
}
}
The console app (net6.0) code:
using ClassLibrary1;
MyModel model1 = new MyModel();
string json1 = Serializer.Serialize(model1);
string json2 = Serializer.SerializeWithGenerics(model1);
Console.WriteLine(json1);
Console.WriteLine(json2);
Console.ReadKey();
Expected behavior
This should work exactly the same in .NET 6 and .NET Standard 2.0, given that the System.Text.Json is multi-targeting.
Actual behavior
It fails in .NET Standard 2.0 with the exception above.
Regression?
No response
Known Workarounds
No response
Configuration
.NET 6.0.100
Visual Studio 2022 17.0.1
Windows 10 20H2
Other information
No response
Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.
Issue Details
Description
I have a .NET 6 app that references a .NET Standard 2.0 class library, which in turn uses the new System.Text.Json source generator to speed up serialization.
When running I get the following exception (this stack trace is from a project used to reproduce the error):
System.MissingMethodException
HResult=0x80131513
Message=Method not found: 'Void System.Text.Json.Serialization.Metadata.JsonObjectInfoValues`1.set_ObjectCreator(System.Func`1<!0>)'.
Source=ClassLibrary1
StackTrace:
at ClassLibrary1.SerializerContext.get_MyModel() in D:\PrjNET\_Experiments\SystemTextJsonSerializerContext\ClassLibrary1\System.Text.Json.SourceGeneration\System.Text.Json.SourceGeneration.JsonSourceGenerator\SerializerContext.MyModel.g.cs:line 37
at ClassLibrary1.Serializer.Serialize(MyModel model) in D:\PrjNET\_Experiments\SystemTextJsonSerializerContext\ClassLibrary1\Serializer.cs:line 9
at Program.<Main>$(String[] args) in D:\PrjNET\_Experiments\SystemTextJsonSerializerContext\ConsoleApp1\Program.cs:line 5
This DOES not happen if the class library targets .NET 6.
Reproduction Steps
I've created a sample to reproduce this so I could eliminate the possibility of something being wrong with the original projects (upgraded from .NET 5).
The class library (netstandard2.0) code:
namespace ClassLibrary1
{
public class MyModel
{
public string Value
{
get; set;
}
}
public partial class Serializer
{
public static string Serialize(MyModel model)
{
return JsonSerializer.Serialize(
model,
SerializerContext.Default.MyModel);
}
public static string SerializeWithGenerics<T>(T model)
{
return JsonSerializer.Serialize(
model,
typeof(T),
SerializerContext.Default);
}
}
[JsonSerializable(typeof(MyModel))]
internal partial class SerializerContext : JsonSerializerContext
{
}
}
The console app (net6.0) code:
using ClassLibrary1;
MyModel model1 = new MyModel();
string json1 = Serializer.Serialize(model1);
string json2 = Serializer.SerializeWithGenerics(model1);
Console.WriteLine(json1);
Console.WriteLine(json2);
Console.ReadKey();
Expected behavior
This should work exactly the same in .NET 6 and .NET Standard 2.0, given that the System.Text.Json is multi-targeting.
Actual behavior
It fails in .NET Standard 2.0 with the exception above.
Regression?
No response
Known Workarounds
No response
Configuration
.NET 6.0.100
Visual Studio 2022 17.0.1
Windows 10 20H2
Other information
No response
| Author: | hugoqribeiro |
|---|---|
| Assignees: | - |
| Labels: |
|
| Milestone: | - |
Can you share the repro project? I tried to repro and it worked for me: https://github.com/ericstj/sample-code/tree/jsonRepro61737/jsonRepro61737
This issue has been marked needs more info since it may be missing important information needed to assess it. Please refer to our contribution guidelines for tips on how to report issues effectively.
@ericstj yours is working because the client lib targets .net 6, not .net standard 2.0.
See here please: https://github.com/hugoqribeiro/trial-n-error/tree/main/System.Text.Json.Issue.61737
I can reproduce -- see here for a minimal repro without sourcegen. The issue reproduces with net6.0 console apps only, it works fine in netcoreapp3.1 and net5.0.
This seems related to exposing init-only properties in netstandard2.0 TFMs. When ClassLibrary1 in the repro is compiled as a net6.0 artifact, the setter method is called as follows:
IL_0007: callvirt instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) class set_ObjectCreator
whereas in the netstadard2.0 build it's
IL_0007: callvirt instance void modreq([System.Text.Json]System.Runtime.CompilerServices.IsExternalInit) class set_ObjectCreator
I suspect that the runtime cannot bind to the right method because the modifier types don't match. net5.0 and netcoreapp3.1 apps don't suffer from the issue because they're consuming the netcoreapp3.1 build of System.Text.Json, which also embeds its own IsExternalInit.
Related to dotnet/roslyn#45510
yours is working because the client lib targets .net 6, not .net standard 2.0
Oops, my apologies. Further illustration that repro projects are good to share so we can see the problem ;) Excellent find by the way, this is a very interesting and subtle bug.
whereas in the netstadard2.0 build it's ...
[System.Text.Json]System.Runtime.CompilerServices.IsExternalInit
Interesting. @eiriktsarpalis could you try explicitly adding a type-forward in the 6.0 build of System.Text.Json for this type and see if it resolves the issue? It's curious that the compiler leaks knowledge of this internal type to customer assembly. That feels like a violation of the visibility contract (and might require us to add a check for this to our API compat infra cc @safern).
I'd be curious what @jaredpar thinks about the compiler emitting a reference to an internal type here.
This is an interesting scenario, that we should definitely explore guarding on API Compat.
Pending @jaredpar's comment on the validity of compiler behavior here, I could imagine an effective visibility rule in API Compat as:
if public method has a modreq with a reference to an internal type, treat that type as if it were public, and make sure it's satisfied in compatible assemblies. Perhaps another version of the rule: treat the types referenced by modreqs as part of the signature that must be satisfied in compatible assemblies. I can imagine this might get a little dicey in our current API compat impl that plays it loose with type-names vs explicit type equivalence.
if public method has a modreq with a reference to an internal type, treat that type as if it were public, and make sure it's satisfied in compatible assemblies
This is a hole we missed with init properties. We did not enforce that the accessibility matched the property. By the time we noticed it was too late, too many people had taken a dependency and it was impossible to make it an error.
I don't quite understand the details.
I've make a repro to test the .NET Standard 2.0 JsonContext running in .NET 6.0 and .NET Framework 4.8 test project. The result is strange to me.
NetStandard20JsonContext passed in NetFramework48PersonTests, but not in Net60PersonTests.
Net60JsonContext passed in Net60PersonTests.
For the clue of init, I try to add IsExternalInit NuGet package into NetStandard20 project, but have no luck.
The result means I need to add .Net Standard 2.0 JsonContext for .NET Framework 4.8 project. And also add .NET 6.0 JsonContext for .NET 6.0 project. I can't find a way to make the two JsonContexts merged.
I've changed my repro to make sure the NetStandard20 project can surely supports init properties after installing IsExternalInit.
And then I change NetStandard20 project to multi-target to netstandard2.0;net6.0. All tests are passed. But I still prefer not to go multi-targeted.
Reopen for servicing
Addressed in 6.0 by #63520.