web-dev-qa-db-ja.com

Json.NETはタイプのシリアル化情報をキャッシュしますか?

.NETの世界では、オブジェクトのシリアル化に関しては、通常、実行時にオブジェクトのフィールドとプロパティを検査します。このジョブにリフレクションを使用することは通常遅く、オブジェクトの大きなセットを扱う場合には望ましくありません。もう1つの方法は、IL放出を使用するか、リフレクションよりもパフォーマンスが大幅に向上する式ツリーを構築することです。そして後者は、シリアル化を扱うときに最も現代的な図書館が選ぶものです。ただし、実行時にILを構築して発行するには時間がかかり、この情報がキャッシュされて同じタイプのオブジェクトに再利用された場合にのみ、投資が回収されます。

Json.NETを使用する場合、上記のどの方法が使用されているか、そして後者が実際に使用されている場合は、キャッシュが使用されているかどうかはわかりません。

たとえば、私がするとき:

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

Json.NETはFooのメンバーアクセス情報とキャッシュを構築して後で再利用しますか?

26
KFL

Json.NETは、タイプのシリアル化情報をその IContractResolver クラス DefaultContractResolver および CamelCasePropertyNamesContractResolver 内にキャッシュします。カスタムコントラクトリゾルバーを指定しない限り、この情報はキャッシュされて再利用されます。

DefaultContractResolverの場合、グローバル静的インスタンスは内部的に維持され、アプリケーションが独自のコントラクトリゾルバーを指定しない場合は常にJson.NETが使用します。一方、CamelCasePropertyNamesContractResolverは、すべてのインスタンスで共有される静的テーブルを維持します。 (不整合はレガシーの問題から生じると思います。詳細については ここ を参照してください。)

これらのタイプは両方とも完全にスレッドセーフになるように設計されているため、スレッド間の共有は問題になりません。

独自のコントラクトリゾルバーを作成することを選択した場合、タイプ情報は、コントラクトリゾルバーインスタンス自体をキャッシュして再利用する場合にのみキャッシュされ、再利用されます。したがって、Newtonsoft 推奨

パフォーマンスのために、コントラクトリゾルバーを一度作成し、可能な場合はインスタンスを再利用する必要があります。コントラクトの解決は遅く、IContractResolverの実装は通常コントラクトをキャッシュします。

DefaultContractResolverのサブクラスでのキャッシュを保証するための1つの戦略は、そのコンストラクターを保護またはプライベートにし、グローバル静的インスタンスを提供することです。 (もちろん、これはリゾルバーが「ステートレス」であり、常に同じ結果を返す場合にのみ適切です。)たとえば、 この質問 に触発されて、契約リゾルバーを強調するパスカルケースを次に示します。

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 });

(注-この特定のリゾルバーのユーティリティは、 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の パフォーマンスのヒント:コントラクトリゾルバーの再利用 を参照してください。

25
dbc