Json.NET是否缓存类型的序列化信息?

KFL*_*KFL 24 c# serialization json expression-trees json.net

在.NET世界中,当谈到对象序列化时,它通常用于在运行时检查对象的字段和属性.对此作业使用反射通常很慢,并且在处理大量对象时是不合需要的.另一种方法是使用IL发射或构建表达树,这些表现树相对于反射提供显着的性能增益.而后者是处理序列化时最现代化的库.但是,在运行时构建和发送IL需要花费时间,并且只有在将此信息缓存并重用于相同类型的对象时才会回收投资.

当使用Json.NET时,我不清楚使用上述哪种方法,如果确实使用了后者,是否使用了缓存.

例如,当我这样做时:

JsonConvert.SerializeObject(new Foo { value = 1 });
Run Code Online (Sandbox Code Playgroud)

Json.NET是否构建了Foo的成员访问信息并缓存以便以后重用它?

dbc*_*dbc 27

Json.NET缓存其内部类型的序列信息IContractResolverDefaultContractResolverCamelCasePropertyNamesContractResolver.除非您指定自定义合同解析程序,否则将缓存并重复使用此信息.

对于DefaultContractResolver全局静态实例,内部维护Json.NET在应用程序未指定自己的合同解析程序时使用. CamelCasePropertyNamesContractResolver另一方面,维护所有实例共享的静态表.(我认为这种不一致性来自遗留问题; 详情请见此处.)

这两种类型都设计为完全线程安全的,因此线程之间的共享应该不是问题.

如果您选择制作自己的合同解析程序,则只有在缓存并重用合同解析程序实例本身时,才会缓存并重用类型信息.因此,Newtonsoft 建议:

为了提高性能,您应该创建一次合同解析程序,并在可能的情况下重用实例.解决合同的速度很慢,IContractResolver的实现通常会缓存合同.

保证子类中缓存的一种策略DefaultContractResolver是使其构造函数受到保护或私有,并提供全局静态实例.(当然这只适用于解析器是"无状态"并且总会返回相同的结果.)例如,受这个问题的启发,是一个强调合同解析器的pascal案例:

public class PascalCaseToUnderscoreContractResolver : DefaultContractResolver
{
    protected PascalCaseToUnderscoreContractResolver() : base() { }

    // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
    // http://www.newtonsoft.com/json/help/html/ContractResolver.htm
    // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
    // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
    static PascalCaseToUnderscoreContractResolver instance;

    // Using an explicit static constructor enables lazy initialization.
    static PascalCaseToUnderscoreContractResolver() { instance = new PascalCaseToUnderscoreContractResolver(); }

    public static PascalCaseToUnderscoreContractResolver Instance { get { return instance; } }

    static string PascalCaseToUnderscore(string name)
    {
        if (name == null || name.Length < 1)
            return name;
        var sb = new StringBuilder(name);
        for (int i = 0; i < sb.Length; i++)
        {
            var ch = char.ToLowerInvariant(sb[i]);
            if (ch != sb[i])
            {
                if (i > 0) // Handle flag delimiters
                {
                    sb.Insert(i, '_');
                    i++;
                }
                sb[i] = ch;
            }
        }
        return sb.ToString();
    }

    protected override string ResolvePropertyName(string propertyName)
    {
        return PascalCaseToUnderscore(propertyName);
    }
}
Run Code Online (Sandbox Code Playgroud)

您将使用哪个:

var json = JsonConvert.SerializeObject(someObject, new JsonSerializerSettings { ContractResolver = PascalCaseToUnderscoreContractResolver.Instance });
Run Code Online (Sandbox Code Playgroud)

(注意 - 这个特定的旋转变压器的效用随着引入而减少了SnakeCaseNamingStrategy.它只是作为一个说明性的例子.)

如果内存消耗是一个问题,无论出于何种原因,您需要最小化缓存合同永久占用的内存,您可以构建自己的本地实例DefaultContractResolver(或某个自定义子类),使用它进行序列化,然后立即删除对它的所有引用,例如:

public class JsonExtensions
{
    public static string SerializeObjectNoCache<T>(T obj, JsonSerializerSettings settings = null)
    {
        settings = settings ?? new JsonSerializerSettings();
        if (settings.ContractResolver == null)
            // To reduce memory footprint, do not cache contract information in the global contract resolver.
            settings.ContractResolver = new DefaultContractResolver();
        return JsonConvert.SerializeObject(obj, settings);
    }
}
Run Code Online (Sandbox Code Playgroud)

大多数缓存的合同内存最终都会被垃圾收集.当然,通过这样做,序列化性能可能会受到很大影响.