Communityコーディング&開発github.com

シリアライゼーション

.NETアプリケーションに適したシリアライゼーションフォーマットを選択します。リフレクションベース(Newtonsoft.Json)よりもスキーマベースのフォーマット(Protobuf、MessagePack)を推奨します。JSONシナリオでは、AOTソースジェネレーターと共にSystem.Text.Jsonを使用します。

シリアライゼーション とは?

このスキルは、.NETアプリケーションにおけるシリアライゼーションフォーマットの選択と実装に関するベストプラクティスを提供します。主な焦点は、スキーマベースのフォーマット(Protobuf、MessagePack、System.Text.Jsonソース生成)をリフレクションベースのフォーマット(Newtonsoft.Json、BinaryFormatter)よりも優先することです。このスキルは、REST API、gRPC、アクターメッセージング、イベントソーシング、キャッシュ、設定、ロギングなど、さまざまなユースケースに対応したフォーマットを推奨します。また、System.Text.Jsonのソースジェネレーターを使用するための実践的なセットアップと使用例、ProtobufとMessagePackの統合、Newtonsoft.Jsonからの移行パターン、耐障害性のあるリーダーや「読み取りを先に導入、書き込みは後で」などのワイヤ互換性パターンについても説明しています。このスキルは、Claude Code、Cursor、CodexなどのAIエージェントツールで使用するために設計されており、開発者がシリアライゼーションに関して情報に基づいた決定を下し、パフォーマンス、AOT互換性、長期的な保守性を確保できるようにします。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aaronontheweb/dotnet-skills/tree/main/skills/serialization

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

.NETにおけるシリアライゼーション

このスキルを使用するタイミング

このスキルは、以下の場合に使用します:

  • API、メッセージング、永続化のためのシリアライゼーションフォーマットを選択する
  • Newtonsoft.JsonからSystem.Text.Jsonに移行する
  • AOT互換のシリアライゼーションを実装する
  • 分散システム向けのワイヤフォーマットを設計する
  • シリアライゼーションのパフォーマンスを最適化する

スキーマベース vs リフレクションベース

アスペクトスキーマベースリフレクションベース
例Protobuf、MessagePack、System.Text.Json(ソース生成)Newtonsoft.Json、BinaryFormatter
ペイロード内の型情報なし(外部スキーマ)あり(型名が埋め込まれる)
バージョニング明示的なフィールド番号/名前暗黙的(型構造)
パフォーマンス高速(リフレクションなし)低速(ランタイムリフレクション)
AOT互換性ありなし
ワイヤ互換性優れている劣る

推奨事項: プロセス境界を越えるものには、スキーマベースのシリアライゼーションを使用します。


フォーマットの推奨事項

ユースケース推奨フォーマット理由
REST APISystem.Text.Json(ソース生成)標準、AOT互換
gRPCProtocol Buffersネイティブフォーマット、優れたバージョニング
アクターメッセージングMessagePack または Protobufコンパクト、高速、バージョンセーフ
イベントソーシングProtobuf または MessagePack古いイベントを永久に処理する必要がある
キャッシュMessagePackコンパクト、高速
設定JSON(System.Text.Json)人間が読み取り可能
ロギングJSON(System.Text.Json)構造化、解析可能

避けるべきフォーマット

フォーマット問題
BinaryFormatterセキュリティ脆弱性、非推奨、絶対に使用しないでください
Newtonsoft.Json デフォルトペイロード内の型名が名前変更時に壊れる
DataContractSerializer複雑、バージョニングが不十分
XML冗長、低速、複雑

ソースジェネレーターを使用した System.Text.Json

JSONシリアライゼーションには、AOT互換性とパフォーマンスのために、ソースジェネレーターと共にSystem.Text.Jsonを使用します。

セットアップ

// すべての型を使用してJsonSerializerContextを定義します
[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(OrderItem))]
[JsonSerializable(typeof(Customer))]
[JsonSerializable(typeof(List<Order>))]
[JsonSourceGenerationOptions(
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
public partial class AppJsonContext : JsonSerializerContext { }

使用法

// コンテキストを使用してシリアライズ
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);

// コンテキストを使用してデシリアライズ
var order = JsonSerializer.Deserialize(json, AppJsonContext.Default.Order);

// ASP.NET Coreで設定
builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});

利点

  • 実行時にリフレクションなし - すべての型情報がコンパイル時に生成されます
  • AOT互換 - Native AOTパブリッシュで動作します
  • より高速 - 実行時の型分析がありません
  • トリムセーフ - リンカーは何が必要かを正確に把握しています

Protocol Buffers (Protobuf)

最適な用途: アクターシステム、gRPC、イベントソーシング、長期にわたるワイヤフォーマット。

セットアップ

dotnet add package Google.Protobuf
dotnet add package Grpc.Tools

スキーマの定義

// orders.proto
syntax = "proto3";

message Order {
    string id = 1;
    string customer_id = 2;
    repeated OrderItem items = 3;
    int64 created_at_ticks = 4;

    // 新しいフィールドの追加は常に安全です
    string notes = 5;  // v2で追加 - 古いリーダーは無視します
}

message OrderItem {
    string product_id = 1;
    int32 quantity = 2;
    int64 price_cents = 3;
}

バージョニングルール

// 安全: 新しい番号で新しいフィールドを追加
message Order {
    string id = 1;
    string customer_id = 2;
    string shipping_address = 5;  // 新規 - 安全
}

// 安全: フィールドを削除(古いリーダーは不明なフィールドを無視、新しいリーダーはデフォルト値を使用)
// フィールドの使用を停止し、番号を予約しておきます
message Order {
    string id = 1;
    // customer_id は削除されましたが、フィールド2は予約されています
    reserved 2;
}

// 安全でない: フィールドタイプを変更
message Order {
    int32 id = 1;  // 以前: string - 壊れます!
}

// 安全でない: フィールド番号を再利用
message Order {
    reserved 2;
    string new_field = 2;  // 2を再利用 - 壊れます!
}

MessagePack

最適な用途: 高性能シナリオ、コンパクトなペイロード、アクターメッセージング。

セットアップ

dotnet add package MessagePack
dotnet add package MessagePack.Annotations

コントラクトを使用した使用法

[MessagePackObject]
public sealed class Order
{
    [Key(0)]
    public required string Id { get; init; }

    [Key(1)]
    public required string CustomerId { get; init; }

    [Key(2)]
    public required IReadOnlyList<OrderItem> Items { get; init; }

    [Key(3)]
    public required DateTimeOffset CreatedAt { get; init; }

    // 新しいフィールド - 古いリーダーは不明なキーをスキップします
    [Key(4)]
    public string? Notes { get; init; }
}

// シリアライズ
var bytes = MessagePackSerializer.Serialize(order);

// デシリアライズ
var order = MessagePackSerializer.Deserialize<Order>(bytes);

AOT互換セットアップ

// AOT用にソースジェネレーターを使用
[MessagePackObject]
public partial class Order { }  // partialによりソース生成が有効になります

// リゾルバーを設定
var options = MessagePackSerializerOptions.Standard
    .WithResolver(CompositeResolver.Create(
        GeneratedResolver.Instance,  // 生成
        StandardResolver.Instance));

Newtonsoft.Jsonからの移行

一般的な問題

NewtonsoftSystem.Text.Json修正方法
JSONの $typeデフォルトではサポートされていません判別子またはカスタムコンバーターを使用
JsonPropertyJsonPropertyName異なる属性
DefaultValueHandlingDefaultIgnoreCondition異なるAPI
NullValueHandlingDefaultIgnoreCondition異なるAPI
プライベートセッター[JsonInclude] が必要明示的なオプトイン
ポリモーフィズム[JsonDerivedType] (.NET 7+)明示的な判別子

移行パターン

// Newtonsoft(リフレクションベース)
public class Order
{
    [JsonProperty("order_id")]
    public string Id { get; set; }

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string? Notes { get; set; }
}

// System.Text.Json(ソース生成互換)
public sealed record Order(
    [property: JsonPropertyName("order_id")]
    string Id,

    string? Notes  // JsonSerializerOptionsを介したnull処理
);

[JsonSerializable(typeof(Order))]
[JsonSourceGenerationOptions(
    PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
public partial class OrderJsonContext : JsonSerializerContext { }

判別子を使用したポリモーフィズム

// .NET 7+ ポリモーフィズム
[JsonDerivedType(typeof(CreditCardPayment), "credit_card")]
[JsonDerivedType(typeof(BankTransferPayment), "bank_transfer")]
public abstract record Payment(decimal Amount);

public sealed record CreditCardPayment(decimal Amount, string Last4) : Payment(Amount);
public sealed record BankTransferPayment(decimal Amount, string AccountNumber) : Payment(Amount);

// 次のようにシリアライズされます:
// { "$type": "credit_card", "amount": 100, "last4": "1234" }

ワイヤ互換性パターン

耐障害性のあるリーダー

古いコードは、未知のフィールドを安全に無視する必要があります:

// Protobuf/MessagePack: 自動 - 未知のフィールドはスキップされます
// System.Text.Json: 許可するように設定
var options = new JsonSerializerOptions
{
    UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip
};

読み取りを先に導入、書き込みは後で

新しいフォーマットのシリアライザーより先にデシリアライザーを展開します:

// フェーズ1: デシリアライザーを追加(すべての場所に展開)
public Order Deserialize(byte[] data, string manifest) => manifest switch
{
    "Order.V1" => DeserializeV1(data),
    "Order.V2" => DeserializeV2(data),  // 新規 - V2を読み取り可能
    _ => throw new NotSupportedException()
};

// フェーズ2: シリアライザーを有効化(次のリリース、V1がすべての場所に展開された後)
public (byte[] data, string manifest) Serialize(Order order) =>
    _useV2Format
        ? (SerializeV2(order), "Order.V2")
        : (SerializeV1(order), "Order.V1");

型名を埋め込まない

// 悪い例: ペイロード内の型名 - クラスの名前変更でワイヤフォーマットが壊れます
{
    "$type": "MyApp.Order, MyApp.Core",
    "id": "123"
}

// 良い例: 明示的な判別子 - リファクタリングセーフ
{
    "type": "order",
    "id": "123"
}

パフォーマンス比較

おおよそのスループット(高いほど良い):

フォーマットシリアライズデシリアライズサイズ
MessagePack★★★★★★★★★★★★★★★
Protobuf★★★★★★★★★★★★★★★
System.Text.Json(ソース生成)★★★★☆★★★★☆★★★☆☆
System.Text.Json(リフレクション)★★★☆☆★★★☆☆★★★☆☆
Newtonsoft.Json★★☆☆☆★★☆☆☆★★★☆☆

ホットパスでは、MessagePackまたはProtobufを優先します。


Akka.NETシリアライゼーション

Akka.NETアクターシステムでは、スキーマベースのシリアライゼーションを使用します:

akka {
  actor {
    serializers {
      messagepack = "Akka.Serialization.MessagePackSerializer, Akka.Serialization.MessagePack"
    }
    serialization-bindings {
      "MyApp.Messages.IMessage, MyApp" = messagepack
    }
  }
}

Akka.NETシリアライゼーションドキュメントを参照してください。


ベストプラクティス

実行すべきこと

// System.Text.Jsonにソースジェネレーターを使用
[JsonSerializable(typeof(Order))]
public partial class AppJsonContext : JsonSerializerContext { }

// 明示的なフィールド番号/キーを使用
[MessagePackObject]
public class Order
{
    [Key(0)] public string Id { get; init; }
}

// 不変メッセージタイプにレコードを使用
public sealed record OrderCreated(OrderId Id, CustomerId CustomerId);

実行すべきでないこと

// BinaryFormatterを使用しないでください(絶対に)
var formatter = new BinaryFormatter();  // セキュリティリスク!

// ワイヤフォーマットに型名を埋め込まないでください
settings.TypeNameHandling = TypeNameHandling.All;  // 名前変更時に壊れます!

// ホットパスにリフレクションシリアライゼーションを使用しないでください
JsonConvert.SerializeObject(order);  // 低速、AOT非互換

リソース

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

akka-hosting-actor-patterns

Patterns for building entity actors with Akka.Hosting - GenericChildPerEntityParent, message extractors, cluster sharding abstraction, akka-reminders, and ITimeProvider. Supports both local testing and clustered production modes.

akka-net-aspire-configuration

Configure Akka.NET with .NET Aspire for local development and production deployments. Covers actor system setup, clustering, persistence, Akka.Management integration, and Aspire orchestration patterns.

akka-net-best-practices

Critical Akka.NET best practices including EventStream vs DistributedPubSub, supervision strategies, error handling, Props vs DependencyResolver, work distribution patterns, and cluster/local mode abstractions for testability.

akka-net-management

Akka.Management for cluster bootstrapping, service discovery (Kubernetes, Azure, Config), health checks, and dynamic cluster formation without static seed nodes.

akka-net-testing-patterns

Write unit and integration tests for Akka.NET actors using modern Akka.Hosting.TestKit patterns. Covers dependency injection, TestProbes, persistence testing, and actor interaction verification. Includes guidance on when to use traditional TestKit.

api-design

Design stable, compatible public APIs using extend-only design principles. Manage API compatibility, wire compatibility, and versioning for NuGet packages and distributed systems.

aspire-configuration

Configure Aspire AppHost to emit explicit app config via environment variables; keep app code free of Aspire clients and service discovery.

aspire-integration-testing

Write integration tests using .NET Aspire

aspire-service-defaults

Create a shared ServiceDefaults project for Aspire applications. Centralizes OpenTelemetry, health checks, resilience, and service discovery configuration across all services.

crap-analysis

Analyze code coverage and CRAP (Change Risk Anti-Patterns) scores to identify high-risk code. Use OpenCover format with ReportGenerator for Risk Hotspots showing cyclomatic complexity and untested code paths.

csharp-concurrency-patterns

Choosing the right concurrency abstraction in .NET - from async/await for I/O to Channels for producer/consumer to Akka.NET for stateful entity management. Avoid locks and manual synchronization unless absolutely necessary.

database-performance

Database access patterns for performance. Separate read/write models, avoid N+1 queries, use AsNoTracking, apply row limits, and never do application-side joins. Works with EF Core and Dapper.

dependency-injection-patterns

Organize DI registrations using IServiceCollection extension methods. Group related services into composable Add* methods for clean Program.cs and reusable configuration in tests.

dotnet-devcert-trust

Diagnose and fix .NET HTTPS dev certificate trust issues on Linux. Covers the full certificate lifecycle from generation to system CA bundle inclusion, with distro-specific guidance for Ubuntu, Fedora, Arch, and WSL2.

dotnet-local-tools

Managing local .NET tools with dotnet-tools.json for consistent tooling across development environments and CI/CD pipelines.

dotnet-project-structure

Modern .NET project structure including .slnx solution format, Directory.Build.props, central package management, SourceLink, version management with RELEASE_NOTES.md, and SDK pinning with global.json.

dotnet-slopwatch

Use Slopwatch to detect LLM reward hacking in .NET code changes. Run after every code modification to catch disabled tests, suppressed warnings, empty catch blocks, and other shortcuts that mask real problems.

efcore-patterns

Entity Framework Core best practices including NoTracking by default, query splitting for navigation collections, migration management, dedicated migration services, and common pitfalls to avoid.

ilspy-decompile

Understand implementation details of .NET code by decompiling assemblies. Use when you want to see how a .NET API works internally, inspect NuGet package source, view framework implementation, or understand compiled .NET binaries.

mailpit-integration

Test email sending locally using Mailpit with .NET Aspire. Captures all outgoing emails without sending them. View rendered HTML, inspect headers, and verify delivery in integration tests.

関連スキル