C# でMessagePackを使ってみた

# まえがき

MessagePack-CSharp（neuecc氏作）を使ってみたときに「はまったこと」「わかったこと」を記述していく

## 環境

IDE:VisualStudio2019
フレームワーク:.NET 5.0

# 基本的な使い方

~~~cs
using MessagePack;
using System;

namespace MessagePackTest
{
    public class Program
    {
        // シリアル化させるオブジェクトには、MessagePackObject属性を付ける。
        // 引数をtrueにするとプロパティの名前がそのまま使用される。
        [MessagePackObject(true)]
        public class MyClass
        {
            public string FirstName { get; set; } = "Hoge";
            public string LastName { get; set; } = "Piyo";
            // シリアル化させたくない場合には、[IgnoreMember]を付ける。
            [IgnoreMember]
            public string FullName { get { return FirstName + LastName; } }
        }

        static void Main()
        {
            var myClass1 = new MyClass();
            // MessagePack形式のバイト配列に変換
            byte[] bytes = MessagePackSerializer.Serialize(myClass1);
            // 復元
            MyClass myClass2 = MessagePackSerializer.Deserialize<MyClass>(bytes);

            // JSONにも変換できる
            var json = MessagePackSerializer.ConvertToJson(bytes);
            Console.WriteLine(json);
        }
    }
}
~~~

出力結果

~~~
{"FirstName":"Hoge","LastName":"Piyo"}
~~~

# 基本的な使い方2
[Key(0)]のように数値を渡すと結果がArray形式になる

~~~cs
～略～
        // シリアル化させるオブジェクトには、MessagePackObject属性を付ける。
        // 引数をfalseにすると[Key()]を各プロパティに付ける必要がある。
        [MessagePackObject(false)]
        public class MyClass
        {
            [Key(0)]
            public string FirstName { get; set; } = "Hoge";
            [Key(1)]
            public string LastName { get; set; } = "Piyo";
            // シリアル化させたくない場合には、[IgnoreMember]を付ける。
            [IgnoreMember]
            public string FullName { get { return FirstName + LastName; } }
        }
～略～
~~~

出力結果

~~~
["Hoge","Piyo"]
~~~

# 基本的な使い方3

[Key("FN")]のように文字列を渡すとMap形式になる

~~~cs
～略～
        // シリアル化させるオブジェクトには、MessagePackObject属性を付ける。
        // 引数をfalseにすると[Key()]を各プロパティに付ける必要がある。
        [MessagePackObject(false)]
        public class MyClass
        {
            [Key("FN")]
            public string FirstName { get; set; } = "Hoge";
            [Key("LN")]
            public string LastName { get; set; } = "Piyo";
            // シリアル化させたくない場合には、[IgnoreMember]を付ける。
            [IgnoreMember]
            public string FullName { get { return FirstName + LastName; } }
        }
～略～
~~~

~~~
{"FN":"Hoge","LN":"Piyo"}
~~~


# エラー例１


~~~cs
using MessagePack;
using System;

namespace MessagePackTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var mc = new MyClass
            {
                Age = 99,
                FirstName = "hoge",
                LastName = "huga",
            };
            byte[] bytes = MessagePackSerializer.Serialize(mc);
            MyClass mc2 = MessagePackSerializer.Deserialize<MyClass>(bytes);

            var json = MessagePackSerializer.ConvertToJson(bytes);
            Console.WriteLine(json);
        }

        [MessagePackObject]
        public class MyClass
        {
            [Key(0)]
            public int Age { get; set; }
            [Key(1)]
            public string FirstName { get; set; }
            [Key(2)]
            public string LastName { get; set; }
            [IgnoreMember]
            public string FullName { get { return FirstName + LastName; } }
        }
    }
}

~~~

上記コードを実行すると、最初の Serialize 関数で以下の例外が発生する。

~~~
～略～
内部例外 1:
TypeInitializationException: The type initializer for 'FormatterCache`1' threw an exception.
内部例外 2:
TypeLoadException: Type 'MessagePack.Formatters.MessagePackTest_Program\+MyClassFormatter1' from assembly 'MessagePack.Resolvers.DynamicObjectResolver, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is attempting to implement an inaccessible interface.

~~~

「アクセスできないインターフェイスｳﾝﾇﾝｶﾝﾇﾝ」とあるので
class Program が public じゃないのが悪かった。
MessagePack 自体の問題ではないけど、VisualStudioで自動生成された部分に間違いがあると思えずはまった。

# 継承したクラスの場合1
特に問題なく動作

~~~cs
using MessagePack;
using System;

namespace MessagePackTest
{
    public class Program
    {
        static void Main(string[] args)
        {
            // {"Hoge":1,"Foo":10}
            Console.WriteLine(MessagePackSerializer.SerializeToJson(new Sample4 { Foo = 10, Bar = 20, Hoge = 1 }));
        }

        [MessagePackObject(keyAsPropertyName: true)]
        public class Sample4 : Base
        {
            public int Foo { get; set; }
            [IgnoreMember]
            public int Bar { get; set; }
        }

        [MessagePackObject(keyAsPropertyName: true)]
        public class Base
        {
            public int Hoge { get; set; }
        }
    }
}
~~~

# 継承したクラスの場合2

* 上記のBaseのkeyAsPropertyNameをfalseにした場合


keyAsPropertyNameがtrueのときと同じ結果となった

~~~cs
他の部分略
        [MessagePackObject(keyAsPropertyName: true)]
        public class Sample4 : Base
        {
            public int Foo { get; set; }
            [IgnoreMember]
            public int Bar { get; set; }
        }

        [MessagePackObject]
        public class Base
        {
            [Key(0)]
            public int Hoge { get; set; }
        }
~~~

# 継承したクラスの場合3

* 継承したクラスの場合1のSample4 のkeyAsPropertyNameをfalseにした場合

ビルドが失敗する（エラー内容は以下の出力）
BaseはkeyAsPropertyName=falseのつもりでも、継承先が優先されるため、[Key()]か[IgnoreMember]をつけろと怒られる

* Baseに[MessagePackObject]を付けなくても同様

~~~cs
他の部分略
        [MessagePackObject]
        public class Sample4 : Base
        {
            [Key(0)]
            public int Foo { get; set; }
            [IgnoreMember]
            public int Bar { get; set; }
        }

        [MessagePackObject(keyAsPropertyName: true)]
        public class Base
        {
            public int Hoge { get; set; }
        }
~~~

## 出力

~~~
ビルドを開始しました...
1>------ ビルド開始: プロジェクト: MessagePackTest, 構成: Debug Any CPU ------
1>D:\Develop\TestProjects\MessagePackTest\MessagePackTest\Program.cs(138,24,138,28): error MsgPack004: Public members of MessagePackObject-attributed types require either KeyAttribute or IgnoreMemberAttribute. Sample4.Hoge.
1>プロジェクト "MessagePackTest.csproj" のビルドが終了しました -- 失敗。
========== ビルド: 0 正常終了、1 失敗、0 更新不要、0 スキップ ==========
~~~

# 継承したクラスの場合4

* 継承したクラスの場合1のBaseにMessagePackObjectが無い場合

継承したクラスの場合1と同様

~~~cs
他の部分略
        [MessagePackObject(keyAsPropertyName:true)]
        public class Sample4 : Base
        {
            public int Foo { get; set; }
            [IgnoreMember]
            public int Bar { get; set; }
        }

        public class Base
        {
            public int Hoge { get; set; }
        }
~~~