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

时间:2015-11-06 01:02:39

标签: c# json serialization json.net expression-trees

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

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

例如,当我这样做时:

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

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

1 个答案:

答案 0 :(得分:23)

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

对于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