如何使用 Newtonsoft.Json 序列化 C# ref 字段?

How to serialize a C# ref field with Newtonsoft.Json?

假设我有一个这样的class:

[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-bytes struct, so I'm returning it by reference in order to avoid the useless copy by value.

我正在使用 JsonConvert.SerializeObject(this, Formatting.Indented) 序列化这样一个 class。

Newtonsoft.Json 转换值时抛出异常,说它无法访问 field/property 值(我猜它是一个 ref 参数使得反射过程被图书馆崩溃)。

我尝试使用自定义 JsonConverter,但在使用任何其他转换器之前就发生了崩溃。

我知道一个快速的解决方案是添加一个次要的私有参数,该参数仅 returns 该字段作为值而不是通过引用,并且仅将其用于 Json 序列化,但只是对我来说看起来很糟糕(我也必须禁用关于未使用的私有参数的自动 VS 警告),如果可能的话,我正在寻找更好的解决方案(不引入无用的 fields/properties)。

感谢您的帮助!

这篇评论太长了,如果有人发布另一个答案我会删除它。快速浏览一下,您目前无法覆盖它。

问题出现在DynamicValueProvider.cs第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);
        }
}

原因在CreateGet,它无法生成正确处理这些类型的方法。也许你应该在 GitHub 上开一个新问题(如果还没有的话)。

您可以在下面看到一个重现该问题的小应用程序:

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);
        }
    }
}