Avoid MissingMethodException on generic classes with init property setters - #1137
Conversation
Repro for failure as reported in MessagePack-CSharp#1134
neuecc
left a comment
There was a problem hiding this comment.
Thank you for the detailed investigation.
In the case of isInit, it looks like it will be !IsWritable.
Does that make it impossible to deserialize normal records as well?
I think it's a bad.
Also, the behavior of quietly returning a null is hard to notice and makes it easy for users to bury serious bugs.
How about throwing an exception for generics and isInit?
The message should be accompanied by the Issue number and correspondence.
No, records can typically deserialize just find. positional records have a constructor that can be used, and for non-positional records (where the properties are written out explicitly) they work as well as classes. So this really has nothing to do with records. It's just any generic class with an explicitly written out
I could totally go that way. In fact an earlier iteration of my change did just that. I'll rework this. |
…y setters Fixes MessagePack-CSharp#1134 as much as we can while .NET has the underlying bug. When .NET 6 ships with the fix, we can add a .NET 6 target that re-allows setting `init` property setters from the `DynamicObjectResolver`.
|
@neuecc I've made the change you requested. |
|
Thank you, I see that the fields used in the ctor are not to be reassigned. using MessagePack;
using MessagePack.Resolvers;
using System;
[MessagePackObject(true)]
public class My
{
public int MyProperty { get; set; } // set or init
public My(int myProperty) /* dummy */
{
}
}
class Program
{
static void Main(string[] args)
{
var m1 = new My(999) { MyProperty = 100 };
var bin = MessagePackSerializer.Serialize(m1);
var m2 = MessagePackSerializer.Deserialize<My>(bin);
// 2.2.60 is `100`
// AArnott:fix1134 is `0`
Console.WriteLine(m2.MyProperty);
}
} |
|
This wasn't intended to have any impact on the scenario you identified, so I'll look more closely. But FWIW I would have expected the "new" behavior. Why would we set the value twice? |
|
First design generate simply return new T(/* constructor parameters */)
{
// all public writable members
}It will be set member in ctor(user defined) -> set field from generated code.
However, Maybe after this PR changed this behaviour ##1095 return new T(/* constructor parameters */)
{
// all public writable members except used in constructor
}This change is okay, I think. |
|
By the way, what about detecting ThrowIfNotWritable in the try-catch and falling back to DynamicMethod? TypeInfo formatterTypeInfo = null;
try
{
formatterTypeInfo = DynamicObjectTypeBuilder.BuildType(DynamicAssembly.Value, typeof(T), false, false);
}
// TODO: use perticular exception
catch (NotSupportedException e) when (e.Message.StartsWith("`init` property accessor"))
{
Formatter = (IMessagePackFormatter<T>)DynamicObjectTypeBuilder.BuildFormatterToDynamicMethod(typeof(T), false, false, false);
return;
}
if (formatterTypeInfo == null)
{
return;
}
Formatter = (IMessagePackFormatter<T>)Activator.CreateInstance(formatterTypeInfo.AsType());which slows down the generation slightly but better thant throw. In this case, we fix |
|
I added a breaking change notice for the upcoming v2.3 release here: https://github.com/neuecc/MessagePack-CSharp/releases/edit/untagged-f7b8032af5f20c994b47 I will update this PR after an attempt to add the fallback path as you suggest. |
|
@neuecc It's done. Please take another look. |
|
sorry for delayed response, I'll check soon. |
Fixes #1134 as much as we can while .NET has the underlying bug.
When .NET 6 ships with the fix, we can add a .NET 6 target that re-allows setting
initproperty setters from theDynamicObjectResolver.