Reputation: 4567
Suppose I have a class like this:
[JsonObject(MemberSerialization.OptIn)]
public class TestClass
{
private readonly int _SomeField;
[JsonProperty(nameof(InputInfo))]
public ref readonly int SomeField => ref _SomeField;
}
Note: this class is a stripped-down example, in my real world scenario that
_SomeField
field is not a member field, otherwise I'd just have added the Json attribute over it. That field is a field exposed by another object which is a member of the class. The public property is just exposing that field to the user, to make it easier to access that value.Also, the actual property tipe is not
int
, but a 12-bytesstruct
, so I'm returning it by reference in order to avoid the useless copy by value.
I'm serializing such a class by using JsonConvert.SerializeObject(this, Formatting.Indented)
.
The Newtonsoft.Json throws an exception when converting the value, saying it can't access the field/property value (I guess it being a ref
parameter makes the reflection procedure used by the library crash).
I tried experimenting with a custom JsonConverter
, but the crash happens before any additional converter is even used.
I know a quick solution would be to add a secondary, private parameter that just returns that field as value and not by reference, and only use it for the Json serialization, but just looks bad to me (and I'd have to disable the automatic VS warning about unused private parameters too), and I'm looking for a better solution, if possible (without introducing useless fields/properties).
Thanks for your help!
Upvotes: 3
Views: 600
Reputation: 7618
This is too long to be a comment,if anyone posts another answer I'll delete it. With a quick look you can't override it at the moment.
The problem occurs in DynamicValueProvider.cs Line 110:
public object GetValue(object target)
{
try
{
if (_getter == null)
{
_getter = DynamicReflectionDelegateFactory.Instance.CreateGet<object>(_memberInfo);
}
return _getter(target); //Line 100
}
catch (Exception ex)
{
throw new JsonSerializationException("Error getting value from '{0}' on '{1}'.".FormatWith(CultureInfo.InvariantCulture, _memberInfo.Name, target.GetType()), ex);
}
}
The cause is in CreateGet
, it can't generate a method to correctly handle these types. Maybe you should open an new issue on GitHub (if there isn't one already).
Below you can see a small app that reproduces the issue:
using System;
using System.Reflection;
using System.Reflection.Emit;
namespace ConsoleApp15
{
public class TestClass
{
public TestClass()
{
_SomeField = 42;
}
private readonly int _SomeField;
public ref readonly int SomeField => ref _SomeField;
}
internal class Program
{
private static void Main(string[] args)
{
var propertyInfo = typeof(TestClass).GetProperty("SomeField");
var getMethod = CreateGet<object>(propertyInfo);
TestClass obj = new TestClass();
var result = getMethod(obj);
}
public static Func<T, object> CreateGet<T>(PropertyInfo propertyInfo)
{
DynamicMethod dynamicMethod = CreateDynamicMethod("Get" + propertyInfo.Name, typeof(object), new[] { typeof(T) }, propertyInfo.DeclaringType);
ILGenerator generator = dynamicMethod.GetILGenerator();
GenerateCreateGetPropertyIL(propertyInfo, generator);
return (Func<T, object>)dynamicMethod.CreateDelegate(typeof(Func<T, object>));
}
private static DynamicMethod CreateDynamicMethod(string name, Type returnType, Type[] parameterTypes, Type owner)
{
DynamicMethod dynamicMethod = new DynamicMethod(name, returnType, parameterTypes, owner, true);
return dynamicMethod;
}
private static void GenerateCreateGetPropertyIL(PropertyInfo propertyInfo, ILGenerator generator)
{
MethodInfo getMethod = propertyInfo.GetGetMethod(true);
if (getMethod == null)
{
throw new ArgumentException("Property " + propertyInfo.Name + " does not have a getter.");
}
if (!getMethod.IsStatic)
{
generator.PushInstance(propertyInfo.DeclaringType);
}
generator.CallMethod(getMethod);
generator.BoxIfNeeded(propertyInfo.PropertyType);
generator.Return();
}
}
internal static class ILGeneratorExtensions
{
public static void PushInstance(this ILGenerator generator, Type type)
{
generator.Emit(OpCodes.Ldarg_0);
if (type.IsValueType)
{
generator.Emit(OpCodes.Unbox, type);
}
else
{
generator.Emit(OpCodes.Castclass, type);
}
}
public static void PushArrayInstance(this ILGenerator generator, int argsIndex, int arrayIndex)
{
generator.Emit(OpCodes.Ldarg, argsIndex);
generator.Emit(OpCodes.Ldc_I4, arrayIndex);
generator.Emit(OpCodes.Ldelem_Ref);
}
public static void BoxIfNeeded(this ILGenerator generator, Type type)
{
if (type.IsValueType)
{
generator.Emit(OpCodes.Box, type);
}
else
{
generator.Emit(OpCodes.Castclass, type);
}
}
public static void UnboxIfNeeded(this ILGenerator generator, Type type)
{
if (type.IsValueType)
{
generator.Emit(OpCodes.Unbox_Any, type);
}
else
{
generator.Emit(OpCodes.Castclass, type);
}
}
public static void CallMethod(this ILGenerator generator, MethodInfo methodInfo)
{
if (methodInfo.IsFinal || !methodInfo.IsVirtual)
{
generator.Emit(OpCodes.Call, methodInfo);
}
else
{
generator.Emit(OpCodes.Callvirt, methodInfo);
}
}
public static void Return(this ILGenerator generator)
{
generator.Emit(OpCodes.Ret);
}
}
}
Upvotes: 2