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

问题描述 投票:19回答:1

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

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

例如,当我这样做时:

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

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

c# json serialization json.net expression-trees
1个回答
23
投票

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

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

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

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

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

保证DefaultContractResolver的子类中的缓存的一种策略是使其构造函数受到保护或私有,并提供全局静态实例。 (当然,这只适用于解析器是“无状态”的并且总会返回相同的结果。)例如,受this question的启发,这是一个强调合同解决方案的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);
    }
}

您将使用哪个:

var json = JsonConvert.SerializeObject(someObject, new JsonSerializerSettings { ContractResolver = PascalCaseToUnderscoreContractResolver.Instance });

(N.B. - 随着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);
    }
}

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

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

© www.soinside.com 2019 - 2024. All rights reserved.