Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
05-16 04:51
관리 메뉴

nomad-programmer

[Programming/C#] 객체 직렬화 (Serialization) 본문

Programming/C#

[Programming/C#] 객체 직렬화 (Serialization)

scii 2020. 9. 28. 20:39

BinaryWriter/Reader 와 StreamWriter/Reader는 기본 데이터 형식을 스트림에 쓰고 읽을 수 있도록 메소드를 제공한다. 하지만 프로그래머가 직접 정의한 클래스나 구조체 같은 복합 데이터 형식은 지원하지 않는다.
BinaryWriter/Reader 와 StreamWriter/Reader로 복합 데이터 형식을 기록하고 읽으려면 그 형식이 갖고 있는 필드의 값을 저장할 순서를 정한 후, 이 순서대로 저장하고 읽는 코드를 작성해야 한다.

이 문제를 위해 C#은 복합 데이터 형식을 쉽게 스트림에 읽기/쓰기를 할 수 있는 하는 "직렬화(Serialization)" 라는 메커니즘을 제공한다.

직렬화란? 객체의 상태(객체의 필드에 저장된 값들을 의미)를 메모리나 영구 저장 장치에 저장이 가능한 0과 1의 순서로 바꾸는 것을 말한다.

.NET의 직렬화가 지원하는 형식

.NET에서는 0과 1의 순서로 구성되는 이진(Binary) 형식으로의 직렬화도 지원하지만 JSON(JavaScript Object Notation)이나 XML같은 텍스트 형식으로의 직렬화도 지원한다. 어떤 형식이든간에 결과만 다르지 직렬화 또는 역직렬화하는 요령은 비슷하다.

Serializable

C# 에서는 객체를 직렬화할 수 있는 아주 간단한 방법을 제공한다. 그저 [Serializable] 어트리뷰트를 클래스 선언부 앞에 붙여주면 이 클래스는 메모리나 영구 저장 장치에 저장이 가능한 형식이 된다.

[Serializable]
class MyClass
{
    // ...
}

이렇게 선언한 형식은 다음과 같이 Stream 클래스와 BinaryFormatter를 이용해서 간단히 저장할 수 있다.

// 직렬화하는 예제 코드

Stream ws = new FileStream("a.dat", FileMode.Create);
BinaryFormatter serializer = new BinaryFormatter();

MyClass obj = new MyClass();

// 직렬화
serializer.Serialize(ws, obj);
ws.Close();

BinaryFormatter 클래스는 System.Runtime.Serialization.Formatter.Binary 네임스페이스에 소속되어 있고 하는 일은 객체를 직렬화(Serialization) 하거나 역직렬화(Deserialization) 하는 것이다.

// 역직렬화하는 예제 코드

Stream rs = new FileStream("a.dat", FileMode.Open);
BinaryFormatter deserializer = new BinaryFormatter();

MyClass obj = (MyClass)deserializer.Deserialize(rs);
rs.Close();

클래스 안에 어떤 필드들이 어떻게 선언되어 있는지 고민할 필요가 없다. 그냥 BinaryFormatter에게 맡기면 객체의 직렬화든 역직렬화든 알아서 해준다.


NonSerialized

상태를 저장하고 싶지 않은 필드(직렬화하고 싶지 않은 필드)가 있다면 다음과 같이 그 필드만 [NonSerialized] 어트리뷰트로 수식해주면 된다.
이렇게 하면 이 필드의 상태는 직렬화할 때도 저장되지 않고, 역직렬화할 때도 역시 복원되지 않는다.

[Serializable]
Class MyClass
{
    public int myField1;
    public int myField2;
    
    // myField3을 제외한 나머지 필드들만 직렬화가된다.
    [NonSerialized]
    public int myField3;
    
    public int myField4;
}   

복합 데이터 형식을 직렬화할 때 주의할 점

Serializable 어트리뷰트를 이용해서 복합 데이터 형식을 직렬화하고자 할 때, 직렬화하지 않는 필드뿐 아니라 직렬화하지 못하는 필드Nonserializable 어트리뷰트로 수식해야 한다. 다음과 같이 Serializable하지 않은 복합 데이터 형식을 필드를 갖는 복합 데이터 형식을 직렬화하고자 하면 오류가 발생한다.

class NonseiralizableClass
{
    public int myField;
}

[Serializable]
class MyClass
{
    public int myField1;
    public int myField2;
    
    // NonserializableClass는 Serializable하지 않으므로 직렬화를 수행할 때 오류 발생!
    public NonserializableClass myField3;
    
    public int myField4;
}

위 코드의 문제를 해결하려면 둘 중 하나를 해야 한다.

  1. NonserializableClass 클래스를 Serializable 어트리뷰트로 수식한다.
  2. myField3을 Nonserializable 어트리뷰트로 수식한다.

Serialization & Deserialization 예제

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace CSharpExample
{
    [Serializable]
    class NameCard
    {
        public string Name;
        [NonSerialized]
        public string Phone;
        public int Age;
    }

    internal class MainApp
    {
        static int Main(string[] args)
        {
            Stream ws = new FileStream("a.dat", FileMode.Create);
            BinaryFormatter serializer = new BinaryFormatter();

            NameCard nc = new NameCard();
            nc.Name = "김연아";
            nc.Phone = "010-5555-0155";
            nc.Age = 31;

            serializer.Serialize(ws, nc);
            ws.Close();

            Stream rs = new FileStream("a.dat", FileMode.Open);
            BinaryFormatter deserializer = new BinaryFormatter();

            NameCard nc2;
            nc2 = (NameCard)deserializer.Deserialize(rs);
            rs.Close();

            Console.WriteLine($"Name : {nc2.Name}");
            Console.WriteLine($"Phone : {nc2.Phone}");
            Console.WriteLine($"Age : {nc2.Age}");

            return 0;
        }
    }
}


/* 결과

Name : 김연아
Phone :
Age : 31

*/

List를 비롯한 컬렉션들도 직렬화를 지원한다. 예를 들어 List<NameCard> 형식의 객체도 직렬화를 통해 파일에 저장해뒀다가 이를 역직렬화를 통해 메모리 내의 컬렉션으로 불러들일 수 있다. 요령은 하나의 객체를 직렬화하고 역직렬화할 때와 동일하다.

컬렉션 직렬화 예제

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace CSharpExample
{
    [Serializable]
    class NameCard
    {
        public string Name;
        public string Phone;
        public int Age;

        public NameCard(string Name, string Phone, int Age)
        {
            this.Name = Name;
            this.Phone = Phone;
            this.Age = Age;
        }
    }

    internal class MainApp
    {
        static int Main(string[] args)
        {
            Stream ws = new FileStream("a.dat", FileMode.Create);
            BinaryFormatter serializer = new BinaryFormatter();

            List<NameCard> list = new List<NameCard>();

            list.Add(new NameCard("김연아", "010-5555-0155", 31));
            list.Add(new NameCard("ethan", "010-5555-5555", 35));
            list.Add(new NameCard("c#", "010-1111-1111", 24));

            serializer.Serialize(ws, list);
            ws.Close();

            Stream rs = new FileStream("a.dat", FileMode.Open);
            BinaryFormatter deserializer = new BinaryFormatter();

            List<NameCard> list2;
            list2 = (List<NameCard>)deserializer.Deserialize(rs);
            rs.Close();

            foreach (NameCard nc in list2)
            {
                Console.WriteLine($"Name: {nc.Name}, Phone: {nc.Phone}, Age: {nc.Age}");
            }

            return 0;
        }
    }
}


/* 결과

Name: 김연아, Phone: 010-5555-0155, Age: 31
Name: ethan, Phone: 010-5555-5555, Age: 35
Name: c#, Phone: 010-1111-1111, Age: 24

*/
Comments