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

Does Json.NET cache types' serialization information?

在 .NET 世界中,当谈到对象序列化时,通常会在运行时检查对象的字段和属性。为这项工作使用反射通常很慢,并且在处理大量对象时是不可取的。另一种方法是使用 IL emit 或构建表达式树,它们比反射提供显着的性能增益。后者是大多数现代图书馆在处理序列化时选择的。但是,在运行时构建和发布 IL 需要时间,只有缓存此信息并将其重新用于相同类型的对象,投资才能得到回报。

在使用Json.NET时,不清楚是用了上面介绍的哪种方法,如果确实用了后者,是否使用了缓存。

例如,当我这样做时:

JsonConvert.SerializeObject(new Foo { value = 1 });

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

是的,确实如此。 Json.NET 在其 IContractResolver classes DefaultContractResolver and CamelCasePropertyNamesContractResolver 中缓存类型序列化信息。除非您指定自定义合同解析器,否则此信息将被缓存并重复使用。

对于DefaultContractResolver,内部维护了一个全局静态实例,只要应用程序未指定其自己的合同解析器,Json.NET就会使用该实例。另一方面,CamelCasePropertyNamesContractResolver 维护在所有实例之间共享的静态表。 (我认为不一致是由遗留问题引起的;有关详细信息,请参阅 。)

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

如果您选择实施和实例化您自己的合同解析器,那么类型信息只有在您缓存和重用合同解析器实例本身时才会被缓存和重用。因此,Newtonsoft recommends:

For performance you should create a contract resolver once and reuse instances when possible. Resolving contracts is slow and implementations of IContractResolver typically cache contracts.

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

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

如果您使用的是 CamelCasePropertyNamesContractResolver,请使用适当的 naming strategy 切换到 DefaultContractResolver,例如:

settings.ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() };

大部分缓存的合约内存(but not all)最终会被垃圾回收。当然,通过这样做,序列化性能可能会受到很大影响。 (一些包含有关 enum 类型和数据协定属性的反映信息的表在全球范围内共享,而不是回收。)

有关详细信息,请参阅 Newtonsoft 的 Performance Tips: Reuse Contract Resolver