NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    C# 직렬화와 역직렬화. (Serialize and deserialize in C#)

    페이지 정보

    본문

    우선, 직렬화와 역직렬화가 무엇인지 알아볼 필요가 있습니다. 직렬화는 객체를 바이트 또는 텍스트 스트림으로 변환합니다. 반대로 역직렬화는 바이트 또는 텍스트 스트림을 객체로 변환해줍니다. 이런 방식이 자주 사용되지는 않지만, 응용 프로그램(윈도우 또는 웹 프로그램과 같은...)간 데이타를 교환할때 사용됩니다. 물론, Database에 저장한 후 iBatis나 EF와 같은 Mapper를 사용하여 모델 객체를 복구해도 됩니다. 이외에도 데이타를 주고 받기 위한 솔루션은 많이 있습니다. 하지만, 직렬화와 역직렬화의 목적은 단순히 데이타만을 주고 받기 위한게 아니므로 차이점을 명확하게 알고 있어야 합니다.

     

    현업에서 직렬화와 역직렬화를 사용하는 이유는, 네트워크를 통해 데이타를 주고 받을 때는 네트워크 스트림으로 바이너리 데이타만 보낼 수 있기 때문입니다. 이렇게 어떤 데이타를 바이너리 데이타로 변환하는 작업을 직렬화라 하고, 데이타를 받는쪽에서 바이너리 데이타를 객체로 변환하는 작업을 역직렬화라고 합니다.

     

    .NET은 직렬화와 역직렬화를 쉽게 할 수 있도록 아래와 같은 네임스페이스를 제공합니다.

     

     

    이 네임스페이스에는 3가지 직렬화 메커니즘을 제공합니다.

     

     

    1. XmlSerializer

    XmlSerializer는 SOAP(Simple Object Access Protocol) 메시지를 지원하기 위해 만들어졌습니다. 이젠 뭐 설명이 필요 없다시피한 유명한 프로토콜이 되었죠. SOAP은 웹 서비스를 위한 프로토콜이지만, 기계와 사람이 모두 읽은 수 있는 XML을 기반으로 하기에 상당히 다양하게 사용되고 있습니다.

     

    웹 서비스가 아니라면, 다음과 같은 예를 들 수 있을거 같네요. 라인에 동일한 장비가 여러대 있을 경우 해당 장비에서 직접 장비 옵션이 정의된 XML을 편집해도 되지만, 서버에서 설정한 후 일괄 적용 시킬수도 있습니다. 이외에도 직접 값을 수정하면서 시뮬레이션 할때도 유용할 수 있습니다.

    Program.cs

    using System;
    using System.IO;
    using System.Xml.Serialization;
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                XmlSerializer serializer = new XmlSerializer(typeof(Human)); 
                string data; 
                
                using (StringWriter stringWriter = new StringWriter())
                {
                    Human human = new Human { Name = "소심비형", Age = 20, Sex = Human.Gender.남성 }; 
                    serializer.Serialize(stringWriter, human); 
                    data = stringWriter.ToString();
                }
    
                Console.WriteLine(data); using (StringReader stringReader = new StringReader(data))
                {
                    Human human = (Human)serializer.Deserialize(stringReader); 
                    Console.WriteLine($"'{human.Name}'은 '{human.Age}'살 '{human.Sex}'입니다.");
                }
    
                Console.ReadKey();
            }
        }
    
        [Serializable]
        public class Human
        {
            public enum Gender { 남성, 여성 }
            public string Name { get; set; }
            public int Age { get; set; }
            public Gender Sex
            {
                get; set;
            }
        }
    }

     

     

    위 예제의 39라인과 같이 Human 클래스에는 Serializable 특성이 부여되어 있습니다. 이 특성이 부여된 클래스는 Xml로 직렬화 할 수 있게 됩니다. 결과는 아래와 같습니다.

    eiD2ckZ.png

     

     

    만약, Human 클래스가 가족이 생겼다고 생각해봅시다. 이런 경우에는 어떻게 처리가 되는지 알아보겠습니다.

    Program.cs

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Xml.Serialization;
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                XmlSerializer serializer = new XmlSerializer(typeof(Human)); 
                string data; 
                
                using (StringWriter stringWriter = new StringWriter())
                {
                    Human human = new Human { Name = "소심비형", Age = 20, Sex = Human.Gender.남성 }; 
                    human.Family.Add(new Human { Name = "대범에이형", Age = 20, Sex = Human.Gender.여성 }); 
                    serializer.Serialize(stringWriter, human); 
                    data = stringWriter.ToString();
                }
    
                Console.WriteLine(data); Console.WriteLine(); using (StringReader stringReader = new StringReader(data))
                {
                    Human human = (Human)serializer.Deserialize(stringReader); 
                    Console.WriteLine($"'{human.Name}'은 '{human.Age}'살 '{human.Sex}'입니다.");
                    Console.WriteLine($"'{human.Family[0].Name}'은 '{human.Family[0].Age}'살 '{human.Family[0].Sex}'입니다.");
                }
    
                Console.ReadKey();
            }
        }
        
        [Serializable]
        public class Human
        {
            public enum Gender { 남성, 여성 }
            public string Name { get; set; }
            public int Age { get; set; }
            public Gender Sex { get; set; }
            public string Position { get; set; }
            public List<Human> Family { get; set; }
            public Human()
            {
                Family = new List<Human>();
            }
        }
    }

     

     

    위 예제의 Human 클래스에 제네릭 컬렉션이 추가되었습니다. 기본 생성자에서 초기화 한 후 Wife를 추가 했죠. 이렇게하면 아래와 같이 배열로 처리되는 것을 확인할 수 있습니다. 이 형식은 소심비형을 중심으로 설계가 되어 있기 때문에 Wife가 하위로 추가됩니다-_-;

    v0a1jgI.png

     

     

    이 외에도 특성을 사용하여 명시적으로 직렬화해야 하는 유형을 알려줄수도 있습니다. 하지만, 직렬화 할 수 없는 경우에는 런타임에 예외를 발생시키게 됩니다. 예를 들면, 위의 예제에서 Family 속성을 Dictionary로 변경하면 에러가 발생됩니다. 이유는 Dictionary같은 경우 Key, Value로 이루어져 있기 때문입니다. System.Xml.Serialization 네임 스페이스에 정의되어 있는 특성은 아래와 같습니다. 명시적으로 특성을 표시하지 않는다면 모든 속성은 기본적으로 XmlElement로 직렬화 됩니다.

     

     

    아래와 같이 각각의 특성을 적용한 후 결과를 확인합니다.

    Program.cs

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Xml.Serialization;
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                XmlSerializer serializer = new XmlSerializer(typeof(Human)); 
                string data;
    
                using (StringWriter stringWriter = new StringWriter())
                {
                    Human human = new Human { Name = "소심비형", Age = 20, Sex = Human.Gender.남성 }; 
                    human.Family.Add(new Human { Name = "대범에이형", Age = 20, Sex = Human.Gender.여성 }); 
                    serializer.Serialize(stringWriter, human);
                    data = stringWriter.ToString();
                }
    
                Console.WriteLine(data); 
                Console.WriteLine();
    
                using (StringReader stringReader = new StringReader(data))
                {
                    Human human = (Human)serializer.Deserialize(stringReader); 
                    Console.WriteLine($"'{human.Name}'은 '{human.Age}'살 '{human.Sex}'입니다.");
                    Console.WriteLine($"'{human.Family[0].Name}'은 '{human.Family[0].Age}'살 '{human.Family[0].Sex}'입니다.");
                }
    
                Console.ReadKey();
            }
        }
    
        [Serializable]
        public class Human
        {
            public enum Gender { 남성, 여성 }
            [XmlAttribute] public string Name { get; set; }
            public int Age { get; set; }
            [XmlElement("Gender")] public Gender Sex { get; set; }
            [XmlIgnore] public string Position { get; set; }
            [XmlArray("Family")] [XmlArrayItem("Human")] public List<Human> Family { get; set; }
            public Human()
            {
                Family = new List<Human>();
            }
        }
    }

     

     

    결과는 아래 그림과 같습니다. Human 클래스의 각 속성이 어떻게 표시되는지 이전 예제와 비교해보세요.

    XvWFm80.png

     

     

    2. BinaryFormatter

    XmlSerializer는 사람이 읽을 수 있는 정보를 기록하는 반면, BinaryFormatter는 제한된 정보만을 확인할 수 있으며 텍스트 편집기로 확인할 수 있습니다. .NET Framework은 바이너리 직렬화를 위해 아래 2개의 네임스페이스를 제공하고 있습니다.

     

     

    아래 예제를 통해 바이너리 직렬화와 역직렬화에 대해서 알아보겠습니다.

    Program.cs

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Xml.Serialization;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                Human human = new Human { Name = "소심비형", Age = 20, Sex = Human.Gender.남성, Position = "남편" };
                IFormatter formatter = new BinaryFormatter();
    
                using (Stream stream = new FileStream("data.bin", FileMode.Create))
                {
                    formatter.Serialize(stream, human);
                }
    
                using (Stream stream = new FileStream("data.bin", FileMode.Open))
                {
                    Human me = (Human)formatter.Deserialize(stream);
                    Console.WriteLine($"'{me.Name}'은 '{me.Age}'살 '{me.Sex}'입니다.");
                }
    
                Console.ReadKey();
            }
        }
    
        [Serializable]
        public class Human
        {
            public enum Gender { 남성, 여성 }
            [XmlAttribute] public string Name { get; set; }
            public int Age { get; set; }
            [XmlElement("Gender")] public Gender Sex { get; set; }
            [XmlIgnore] public string Position { get; set; }
            [XmlArray("Family")] [XmlArrayItem("Human")] public List<Human> Family { get; set; }
            public Human()
            {
                Family = new List<Human>();
            }
        }
    }

     

     

    솔루션을 전체 빌드한 후 솔루션 탐색기에서 콘솔 프로젝트를 선택하고 우클릭합니다. Context 메뉴에서 "파일 탐색기에서 폴더 열기"를 선택하세요.

    FWskAuQ.png

     

     

    프로젝트의 파일 위치가 열리게 됩니다. 아래 이미지처럼 bin > Debug폴더로 이동하세요. 만약, 릴리즈 모드로 빌드했다면, Release폴더로 이동해야 합니다.

    XaLoCVS.png

     

     

    data.bin 파일을 비주얼 스튜디오에서 열어보면, 아래와 같이 바이너리 포멧으로 저장된 것을 확인할 수 있습니다.

    LsFUrrp.png

     

     

    메모장으로 열면 아래와 같이 보여집니다.

    m2rbkrS.png

     

     

    이제 비주얼 스튜디오에서 실행하여 역직렬화가 되는지 확인 해보세요. 아래와 같은 결과를 볼 수 있습니다.

    idb5roN.png

     

     

    바이너리 직렬화는 private field에 대해 기본적으로 직렬화되며, 직렬화하는 동안 생성자는 실행되지 않습니다. 만약, 특정 필드 또는 속성에 대해 직렬화 하지 않으려면 [NonSerialized]특성을 부여할 수 있습니다. 바이너리 직렬화와 XmlSerializer의 다른점은 특정 필드를 찾을 수 없을때 예외 발생 여부입니다. XmlSerializer는 예외를 발생시키지 않습니다.

     

    아래는 메서드에 대해 직렬화와 역직렬화 예제입니다.

    Program.cs

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Xml.Serialization;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                Human human = new Human { Name = "소심비형", Age = 20, Sex = Human.Gender.남성, Position = "남편" }; 
                IFormatter formatter = new BinaryFormatter(); 
                
                using (Stream stream = new FileStream("data.bin", FileMode.Create))
                {
                    formatter.Serialize(stream, human);
                }
    
                using (Stream stream = new FileStream("data.bin", FileMode.Open))
                {
                    Human me = (Human)formatter.Deserialize(stream);
                    Console.WriteLine(me.ToString());
                }
    
                Console.ReadKey();
            }
        }
    
        [Serializable]
        public class Human
        {
            public enum Gender { 남성, 여성 }
    
            [XmlAttribute] 
            public string Name { get; set; }
    
            public int Age { get; set; }
    
            [XmlElement("Gender")] 
            public Gender Sex { get; set; }
    
            [XmlIgnore]
            public string Position { get; set; }
    
            [XmlArray("Family")] 
            [XmlArrayItem("Human")] 
            public List<Human> Family { get; set; }
    
            public Human()
            {
                Family = new List<Human>();
            }
    
            public override string ToString()
            {
                return $"'{this.Name}'은 '{this.Age}'살 '{this.Sex}'입니다.";
            }
        }
    }

     

     

    31라인처럼 메서드를 호출해도 결과는 같습니다.

    직렬화 프로세스에 직접 개입하여 어떤 처리가 필요한 경우 아래 4개의 특성을 사용할 수 있습니다.

     

     

    다음은 직렬화와 역직렬화 프로세스를 제어하는 방법을 보여줍니다.

    Program.cs

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Xml.Serialization;
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                Human human = new Human { Name = "소심비형", Age = 20, Sex = Human.Gender.남성, Position = "남편" };
                IFormatter formatter = new BinaryFormatter(); 
                
                using (Stream stream = new FileStream("data.bin", FileMode.Create))
                {
                    formatter.Serialize(stream, human);
                }
    
                using (Stream stream = new FileStream("data.bin", FileMode.Open))
                {
                    Human me = (Human)formatter.Deserialize(stream); 
                    Console.WriteLine(me.ToString());
                }
    
                Console.ReadKey();
            }
        }
    
        [Serializable]
        public class Human
        {
            [NonSerialized] 
            private string position; 
    
            public enum Gender { 남성, 여성 }
            [XmlAttribute] 
            public string Name { get; set; }
    
            public int Age { get; set; }
    
            [XmlElement("Gender")] 
            public Gender Sex { get; set; }
    
            [XmlIgnore] 
            public string Position { get { return position; } set { position = value; } }
    
            [XmlArray("Family")] 
            [XmlArrayItem("Human")] 
            public List<Human> Family { get; set; }
    
            public Human()
            {
                Family = new List<Human>();
            }
    
            public override string ToString()
            {
                return $"'{this.Name}'은 '{this.Age}'살 '{this.Sex}'입니다.";
            }
    
            [OnSerializing]
            internal void OnSerializingMethod(StreamingContext context)
            {
                Console.WriteLine("OnSerializing");
            }
    
            [OnSerialized]
            internal void OnSerializedMethod(StreamingContext context)
            {
                Console.WriteLine("OnSerialized");
            }
    
            [OnDeserializing]
            internal void OnDeserializingMethod(StreamingContext context)
            {
                Console.WriteLine("OnDeserializing");
            }
    
            [OnDeserialized]
            internal void OnDeserializedMethod(StreamingContext context)
            {
                Console.WriteLine("OnDeserialized");
            }
        }
    }

     

     

    아래는 결과 화면입니다. 이렇게 메서드에 특성을 부여함으로써 직렬화와 역직렬화에 개입할 수 있습니다.

    W2Neo7j.png

     

     

    직렬화된 개체는 보안에 민감한 데이타를 담지 않도록 하는게 좋습니다. 중요한 데이타에 접근할 수 있다면 어떤 문제가 발생될지 알 수 없기 때문입니다. 만약, 중요한 정보를 직렬화해야 한다면, ISerializable 인터페이스를 직접 구현하는게 올바른 방법입니다.

     

    3. DataContractSerializer

    DataContractSerializer는 주로 WCF(Windows Communication Foundation)와 함께 사용되며, XML 또는 JSON으로 개체를 직렬화하는 WCF에 의해 사용되고 있습니다. BinaryFormatter와 차이점은 SerializableAttribute 대신 DataContractAttribute를 사용하는 것입니다. 이외에도 클래스의 맴버가 기본적으로 직렬화되지 않는다는 것입니다. 따라서 직렬화하기 위한 맴버에 대해서는 명시적으로 DataMember특성을 부여해야 합니다.

     

    아래 예제는 DataContractSerializer의 사용 방법을 보여주고 있습니다. 이 예제를 실행하려면 해당 프로젝트의 참조에 System.Runtime.Serialization.dll이 추가되어 있어야 합니다.

    Program.cs

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.ServiceModel;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Xml.Serialization;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                Human human = new Human { Name = "소심비형", Age = 20, Sex = Human.Gender.남성, Position = "남편" };
    
                using (Stream stream = new FileStream("data.xml", FileMode.Create))
                {
                    DataContractSerializer ser = new DataContractSerializer(typeof(Human)); ser.WriteObject(stream, human);
                }
    
                using (Stream stream = new FileStream("data.xml", FileMode.Open))
                {
                    DataContractSerializer ser = new DataContractSerializer(typeof(Human));
                    Human result = (Human)ser.ReadObject(stream);
                    Console.WriteLine(result.ToString());
                }
    
                Console.ReadKey();
            }
        }
    
        [DataContract]
        public class Human
        {
            [NonSerialized] 
            private string position; 
            
            public enum Gender { 남성, 여성 }
    
            [DataMember] 
            public string Name { get; set; }
    
            //[DataMember]        
            public int Age { get; set; }
    
            [DataMember]
            [XmlElement("Gender")]
            public Gender Sex { get; set; }
    
            [XmlIgnore]
            public string Position
            {
                get { return position; }
                set { position = value; }
            }
    
            [DataMember]
            [XmlArray("Family")]
            [XmlArrayItem("Human")]
            public List<Human> Family { get; set; }
    
            public Human()
            {
                Family = new List<Human>();
            }
    
            public override string ToString()
            {
                return $"'{this.Name}'은 '{this.Age}'살 '{this.Sex}'입니다.";
            }
    
            [OnSerializing]
            internal void OnSerializingMethod(StreamingContext context)
            {
                Console.WriteLine("OnSerializing");
            }
    
            [OnSerialized]
            internal void OnSerializedMethod(StreamingContext context)
            {
                Console.WriteLine("OnSerialized");
            }
    
            [OnDeserializing]
            internal void OnDeserializingMethod(StreamingContext context)
            {
                Console.WriteLine("OnDeserializing");
            }
    
            [OnDeserialized]
            internal void OnDeserializedMethod(StreamingContext context)
            {
                Console.WriteLine("OnDeserialized");
            }
        }
    }

     

     

    49라인의 주석을 풀면 정상적으로 데이타가 보여집니다. XmlSerializer, BinaryFormatter와 다르게 기본적으로 직렬화 하지 않습니다. 따라서, 위 예제를 실행 해보면 나이는 0으로 초기화되어 표시되는 것을 알 수 있습니다. 또한, enum의 경우 플래그를 지정하지 않으면 0으로 초기화되므로 사용할 때 유의해야 합니다.

    GbR06qs.png

     

     

    웹의 특성상 비동기식 XML 데이타(AJAX)를 보낼 때는 데이타가 적은 JSON을 사용하는게 효율적입니다. 아작스는 특정 부분만을 갱신시키는데 주로 사용됩니다. 이때 전체 데이타를 주고 받는것은 리소스의 낭비가 크므로, 작은 데이타를 교환할 수 있는 JSON을 이용하게 됩니다.

    Program.cs

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.ServiceModel;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Xml.Serialization;
    using System.Runtime.Serialization.Json;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                Human human = new Human { Name = "소심비형", Age = 20, Sex = Human.Gender.남성, Position = "남편" }; 
                
                using (MemoryStream stream = new MemoryStream())
                {
                    DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(Human)); 
                    ser.WriteObject(stream, human); 
                    stream.Position = 0; 
                    StreamReader streamReader = new StreamReader(stream); 
                    Console.WriteLine(streamReader.ReadToEnd()); 
                    stream.Position = 0; 
                    Human result = (Human)ser.ReadObject(stream); 
                    Console.WriteLine(result.ToString());
                }
    
                Console.ReadKey();
            }
        }
    
        [DataContract]
        public class Human
        {
            [NonSerialized]
            private string position; 
            
            public enum Gender { 남성, 여성 }
    
            [DataMember] 
            public string Name { get; set; }
    
            //[DataMember]  
            public int Age { get; set; }
    
            [DataMember]
            [XmlElement("Gender")]
            public Gender Sex { get; set; }
    
            [XmlIgnore]
            public string Position
            {
                get { return position; }
                set { position = value; }
            }
    
            [DataMember]
            [XmlArray("Family")]
            [XmlArrayItem("Human")] 
            public List<Human> Family { get; set; }
    
            public Human()
            {
                Family = new List<Human>();
            }
    
            public override string ToString()
            {
                return $"'{this.Name}'은 '{this.Age}'살 '{this.Sex}'입니다.";
            }
    
            [OnSerializing]
            internal void OnSerializingMethod(StreamingContext context)
            {
                Console.WriteLine("OnSerializing");
            }
    
            [OnSerialized]
            internal void OnSerializedMethod(StreamingContext context)
            {
                Console.WriteLine("OnSerialized");
            }
    
            [OnDeserializing]
            internal void OnDeserializingMethod(StreamingContext context)
            {
                Console.WriteLine("OnDeserializing");
            }
    
            [OnDeserialized]
            internal void OnDeserializedMethod(StreamingContext context)
            {
                Console.WriteLine("OnDeserialized");
            }
        }
    }

     

     

    위 예제의 결과는 아래와 같습니다.

    xSWtHfN.png

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천1 비추천0

    댓글목록

    profile_image