在.NET世界中,涉及对象序列化时,通常会在运行时检查对象的字段和属性。通常,使用反射进行此工作很慢,并且在处理大量对象时是不可取的。另一种方法是使用IL发射或构建表达式树,这些树比反射提供了显着的性能提升。后者是大多数现代库在处理序列化时选择的。但是,在运行时建立和发布IL会花费时间,并且仅当此信息被缓存并重新用于相同类型的对象时,才可收回投资。
使用Json.NET时,对我来说不清楚使用哪种方法,如果确实使用了后者,则是否使用缓存。
例如,当我这样做时:
JsonConvert.SerializeObject(new Foo { value = 1 });
Json.NET是否构建Foo的成员访问信息并缓存以供日后重用?
是的,它确实。 Json.NET在其IContractResolver类DefaultContractResolver和中缓存类型序列化信息CamelCasePropertyNamesContractResolver。除非您指定自定义合同解析器,否则此信息将被缓存和重用。
IContractResolver
DefaultContractResolver
CamelCasePropertyNamesContractResolver
对于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() };
大部分缓存的协定内存( 但不是全部 )最终将被垃圾回收。当然,这样做 会严重影响序列化性能 。(某些表包含有关enum类型和数据协定属性的反映信息,这些表在全球范围内共享,因此不会被回收。)
enum
有关更多信息, 请参阅Newtonsoft的性能提示:重用合同解析器。