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

36
在.NET世界中,当涉及到对象序列化时,通常需要在运行时检查对象的字段和属性。使用反射执行此任务通常会很慢,而且在处理大量对象时并不理想。另一种方法是使用IL emit或构建表达式树,这可以比反射提供显着的性能提升。后者是现代大多数库在处理序列化时选择的方式。然而,在运行时构建和发出IL需要时间,并且只有在将此信息缓存并重用于相同类型的对象时才能回收投资。
当使用Json.NET时,我不清楚上述哪种方法被使用,如果确实使用后者,是否使用了缓存。
例如,当我执行以下操作时:
JsonConvert.SerializeObject(new Foo { value = 1 });

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


5
我无法给你一个明确的答案,但是Json.NET 的源代码在Github上,并且它确实表示"Json.NET是.NET平台上流行的高性能JSON框架"。如果你在源代码中快速搜索缓存,你会发现确实存在许多缓存操作。 - KiwiPiet
1个回答

41
是的,它会。Json.NET 将类型序列化信息缓存在其IContractResolverDefaultContractResolverCamelCasePropertyNamesContractResolver中。除非您指定自定义合同解析器,否则将缓存并重复使用此信息。
对于DefaultContractResolver,在应用程序未指定其自己的合同解析器时,内部维护全局静态实例,Json.NET 将使用它。另一方面,CamelCasePropertyNamesContractResolver 维护静态表,在所有实例之间共享。(我认为这种不一致性来源于遗留问题,请参见此处了解详情。)

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

如果您选择实现和实例化自己的合同解析器,则只有缓存和重用合同解析器实例本身时,类型信息才会被缓存和重用。 因此,Newtonsoft 建议

为了提高性能,应该创建一个合同解析器并尽可能重复使用实例。 解析合同很慢,而且 IContractResolver 的实现通常会缓存合同。

如果内存消耗是一个问题,并且由于某种原因您需要最小化缓存合同所占用的永久内存,您可以构造自己的本地实例 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 ,请切换到适当的命名策略,例如 DefaultContractResolver

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

大多数缓存的合同内存(但并非全部)最终将被垃圾回收。当然,这样做会导致序列化性能显著下降。(某些包含有关例如枚举类型和数据合同属性的反射信息的表格是全局共享的,不会被回收。) 更多信息请参见Newtonsoft的性能提示:重用合同解析器

我们发现在UTs中,保存在我们的MyContractResolver:CamelCasePropertyNamesContractResolver类型中的合同被静态地缓存,即使跨多个MyContractResolver类型。这会导致一些UTs一起运行时出现意外故障,因为它们没有按照需要设置合同。重用相同的ContractResolver实例的指针仍然有效吗? - ElFik
1
@ElFik - CamelCasePropertyNamesContractResolverDefaultContractResolver的行为不同--它会全局缓存合同信息,无论您是否需要。如果您不想要这个功能,请使用适当的命名策略切换到DefaultContractResolver - dbc
@dbc 和 ElFik,你们救了我的一天!CamelCasePropertyNamesContractResolver 实现的失败太可怕了。非常感谢你们! - brunosouzamelo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接