MCP 70-483 Programming in C# のお勉強 「4-4 データをシリアル化および逆シリアル化する」

MCP試験 [70-483 Programming in C#](https://www.microsoft.com/ja-jp/learning/exam-70-483.aspx) の学習材料。

目次は[こちら](http://qiita.com/takutoy/items/863ab5523cf59173257a)

# 4-4 データをシリアル化および逆シリアル化する

## シリアル化(Serialization)

シリアル化(Serialize)とは、メモリ内のオブジェクトをバイト列やテキストに変換することをいう。逆に、そのバイト列やテキストをオブジェクトに変換することをで逆シリアル化(Deserialize)という。オブジェクトをファイルに保存したり、ネットワーク越しの別のプログラムに渡したいときに必要なプロセス。このシリアル化・逆シリアル化するプログラムのことをシリアライザ(Serializer)という。

※ここで紹介するシリアライザよりも何倍も高速で使いやすいシリアライザが世に出回ってるので、.NET標準ライブラリ縛りプレイをしない限りは使うことは無いかもしれない。

## XmlSerializer
オブジェクトを XML に変換するシリアライザ。昔ながらの XML-RPC を使うときにお世話になるかも。 

```csharp
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int tmpValue;
}

void Main()
{
    Product item = new Product()
    {
        Id = 1,
        Name = "CPU",
        tmpValue = 123
    };

    string serializedData = null;

    // Serialize
    {
        XmlSerializer xs = new XmlSerializer(typeof(Product));
        StringWriter sw = new StringWriter();
        xs.Serialize(sw, item);
        serializedData = sw.ToString();
    }

    Console.WriteLine(serializedData);

    // Deserialize
    {
        XmlSerializer xs = new XmlSerializer(typeof(Product));
        Product deserilizedObject = xs.Deserialize(new StringReader(serializedData)) as Product;
    }
}
```

serializedData の中身

```xml
<?xml version="1.0" encoding="utf-16"?>
<Product xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <tmpValue>123</tmpValue>
  <Id>1</Id>
  <Name>CPU</Name>
</Product>
```

上記サンプルでは TextReader/Writer に(デ)シリアライズしているが、Stream や XmlReader/Writer を使うこともできる。

### XmlSerializerで使うAttribute 

XML をシリアライズまたはシリアライスする際、特定のプロパティだけシリアライズしたくなかったり XML Element ではなく Attribute にしたかったりする場合は、対象プロパティに属性を付けると制御できる。

XmlSerializerに使用できる属性例

- XmlIgnore
- XmlRoot
- XmlAttirbute
- XmlElement("xmltag")
- XmlText
- XmlArray
- XmlArrayItem

```csharp
public class Product
{
    [XmlAttribute]
    public int Id { get; set; }
    
    [XmlAttribute]
    public string Name { get; set; }
    
    [XmlIgnore]
    public int tmpValue;
}
```

```xml
<?xml version="1.0" encoding="utf-16"?>
<Product xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Id="1" Name="CPU" />
```

## BinaryFormatter

XML はサイズがでかい。BinaryFormatter を使うと、比較的小さいサイズのバイナリにシリアライズできる。

```csharp
[Serializable]
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }

    [NonSerialized]
    public int tmpValue;
}

void Main()
{
    Product item = new Product()
    {
        Id = 1,
        Name = "CPU",
        tmpValue = 123
    };

    byte[] serializedData = null;

    // Serialize
    {
        BinaryFormatter bf = new BinaryFormatter();
        MemoryStream mem = new MemoryStream();
        bf.Serialize(mem, item);
        serializedData = mem.ToArray();
    }

    // Deserialize
    {
        BinaryFormatter bf = new BinaryFormatter();
        MemoryStream mem = new MemoryStream(serializedData);
        Product deserilizedObject = bf.Deserialize(mem) as Product;
    }
}
```

serializedData の中身

```text
00000000   00 01 00 00 00 FF FF FF  FF 01 00 00 00 00 00 00   .....ÿÿÿÿ.......
00000010   00 0C 02 00 00 00 43 71  75 65 72 79 5F 6E 63 77   ......Cquery_ncw
00000020   77 6E 6E 2C 20 56 65 72  73 69 6F 6E 3D 30 2E 30   wnn, Version=0.0
00000030   2E 30 2E 30 2C 20 43 75  6C 74 75 72 65 3D 6E 65   .0.0, Culture=ne
00000040   75 74 72 61 6C 2C 20 50  75 62 6C 69 63 4B 65 79   utral, PublicKey
00000050   54 6F 6B 65 6E 3D 6E 75  6C 6C 05 01 00 00 00 11   Token=null......
00000060   55 73 65 72 51 75 65 72  79 2B 50 72 6F 64 75 63   UserQuery+Produc
00000070   74 02 00 00 00 13 3C 49  64 3E 6B 5F 5F 42 61 63   t.....<Id>k__Bac
00000080   6B 69 6E 67 46 69 65 6C  64 15 3C 4E 61 6D 65 3E   kingField.<Name>
00000090   6B 5F 5F 42 61 63 6B 69  6E 67 46 69 65 6C 64 00   k__BackingField.
000000A0   01 08 02 00 00 00 01 00  00 00 06 03 00 00 00 03   ................
000000B0   43 50 55 0B                                        CPU.            
```

### BinarySerializer で使う属性

- Serializable: シリアライズしたいクラスに付ける(付けなければならない)
- NotSerialized: シリアライズしたくないプロパティ、フィールドに付ける

## JavaScriptSerializer

JSON にシリアライズ。使うにはアセンブリ System.Web.Extensions をプロジェクトの参照に追加する。

```csharp
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int tmpValue;
}

void Main()
{
    Product item = new Product()
    {
        Id = 1,
        Name = "CPU",
        tmpValue = 123
    };

    string serializedData = null;

    // Serialize
    {
        JavaScriptSerializer js = new JavaScriptSerializer();
        serializedData = js.Serialize(item);
    }

    Console.WriteLine(serializedData);

    // Deserialize
    {
        JavaScriptSerializer js = new JavaScriptSerializer();
        Product deserilizedObject = js.Deserialize<Product>(serializedData);
    }
}
```

serializedData の中身

```json
{"tmpValue":123,"Id":1,"Name":"CPU"}
```

### JavaScriptSerializer で使う属性

- ScriptIgnore: シリアライズしたくないプロパティ、フィールドに付ける

```csharp
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    
    [ScriptIgnoreAttribute]
    public int tmpValue;
}
```

```json
{"Id":1,"Name":"CPU"}
```

## DataContractSerializer

XML シリアライザその２。主に WCF で使われる。使うにはアセンブリ System.Runtime.Serialization をプロジェクトの参照に追加する。

```csharp
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int tmpValue;
}

void Main()
{
    Product item = new Product()
    {
        Id = 1,
        Name = "CPU",
        tmpValue = 123
    };

    byte[] serializedData = null;

    // Serialize
    {
        DataContractSerializer ds = new DataContractSerializer(typeof(Product));
        MemoryStream mem = new MemoryStream();
        ds.WriteObject(mem, item);
        serializedData = mem.ToArray();
    }

    Console.WriteLine(Encoding.UTF8.GetString(serializedData));

    // Deserialize
    {
        DataContractSerializer bf = new DataContractSerializer(typeof(Product));
        MemoryStream mem = new MemoryStream(serializedData);
        Product deserilizedObject = bf.ReadObject(mem) as Product;
    }
}
```

serializedData の中身

```xml
<UserQuery.Product xmlns="http://schemas.datacontract.org/2004/07/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Id>1</Id><Name>CPU</Name><tmpValue>123</tmpValue></UserQuery.Product>
```

上記サンプルでは Stream に(デ)シリアライズしているが、XmlReader/Writer, XmlDictionaryReader/Writer を使うこともできる。

### DataContractSerializer で使う属性

- DataContract: シリアライズしたいクラスに付ける
- DataMember:  シリアライズしたいプロパティ、フィールドに付ける
- IgnoreDataMember: シリアライズしたくないプロパティ、フィールドに付ける

```csharp
[DataContract]
public class Product
{
    [DataMember]
    public int Id { get; set; }
    [DataMember]
    public string Name { get; set; }
    [IgnoreDataMember]
    public int tmpValue;
}
```

```xml
<UserQuery.Product xmlns="http://schemas.datacontract.org/2004/07/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Id>1</Id><Name>CPU</Name></UserQuery.Product>
```

## DataContractJsonSerializer

DataContractSerializer が XML なのに対し、こちらは JSON. 使い方は全く同じ。

```csharp
[DataContract]
public class Product
{
    [DataMember]
    public int Id { get; set; }
    [DataMember]
    public string Name { get; set; }
    [IgnoreDataMember]
    public int tmpValue;
}

void Main()
{
    Product item = new Product()
    {
        Id = 1,
        Name = "CPU",
        tmpValue = 123
    };

    byte[] serializedData = null;

    // Serialize
    {
        DataContractJsonSerializer ds = new DataContractJsonSerializer(typeof(Product));
        MemoryStream mem = new MemoryStream();
        ds.WriteObject(mem, item);
        serializedData = mem.ToArray();
    }

    Console.WriteLine(Encoding.UTF8.GetString(serializedData));

    // Deserialize
    {
        DataContractJsonSerializer bf = new DataContractJsonSerializer(typeof(Product));
        MemoryStream mem = new MemoryStream(serializedData);
        Product deserilizedObject = bf.ReadObject(mem) as Product;
    }
}
```

serializedData の中身

```json
{"Id":1,"Name":"CPU"}
```

## その他覚えておいた方がよさそうなキーワード

- ISerializable, IXmlSerializable
- OnSerializing, OnSerialized, OnDeserializing, OnDeserialized
- NetDataContractSerializer

## リソース
- [シリアル化 (C#)](https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/serialization/)

- [データ コントラクト シリアライザー](https://msdn.microsoft.com/ja-jp/library/ms731072(v=vs.110).aspx)

- [Custom Serialization Order With XmlSerializer (英語)](https://docs.microsoft.com/ja-jp/dotnet/framework/serialization/custom-serialization-order-with-xmlserializer)

## Quiz
1. XmlSerializer, BinarySerializer, JavaScriptSerializer, DataContract(Json)Serializer それぞれのシリアライザで使用する Attribute とインタフェースを整理しろ
2. 下記の XML を Company オブジェクトに逆シリアル化できるように Company クラスを変更しろ。

```xml
<company>
    <employee id="1" name="suzuki" />
    <employee id="2" name="sato" />
    <employee id="3" name="tanaka" />
    <divisions>
        <division>soumu</division>
        <division>jinji</division>
    </divisions>
</company>
```

```csharp
public class Company
{
    public Employee[] Employees { get; set; }
    public Division[] Divisions { get; set; }
}
```