수색…


비고

이진 직렬화 엔진은 .NET 프레임 워크의 일부이지만 여기에 제공된 예제는 C #에만 해당됩니다. .NET 프레임 워크에 내장 된 다른 직렬화 엔진과 비교할 때 이진 직렬화 기는 빠르고 효율적이며 일반적으로 작동시키기 위해 약간의 추가 코드가 필요합니다. 그러나 코드 변경 사항에 대해서는 관대하지 않습니다. 즉, 객체를 직렬화 한 다음 객체의 정의를 약간 변경하면 올바르게 직렬화되지 않습니다.

직렬화 가능 오브젝트 만들기

이진 직렬화를 위해 전체 객체를 표시하려면 [Serializable] 속성을 추가하십시오.

[Serializable]
public class Vector
{
    public int X;
    public int Y;
    public int Z;

    [NonSerialized]
    public decimal DontSerializeThis;

    [OptionalField]
    public string Name;
}

[NonSerialized] 속성을 사용하여 명시 적으로 거부하지 않는 한 모든 구성원이 직렬화됩니다. 예제에서는 X , Y , ZName 이 모두 serialize됩니다.

[NonSerialized] 또는 [OptionalField] 로 표시하지 않는 한 모든 멤버가 비 직렬화에 있어야합니다. 이 예에서는 X , YZ 가 모두 필요하며 스트림에 스트림이 없으면 비 직렬화가 실패합니다. DontSerializeThis 는 항상 default(decimal) (0)으로 설정됩니다. Name 가 스트림에 존재하면, 그 값으로 설정됩니다. 그렇지 않은 경우는, default(string) (null)가됩니다. [OptionalField] 의 목적은 약간의 버전 공차를 제공하는 것입니다.

특성을 사용한 직렬화 동작 제어

[NonSerialized] 속성을 사용하는 경우 객체 자체에서 초기화가 수행되었는지 여부에 관계없이 직렬화 해제 후에 기본값이 항상 유지됩니다 (예 : int 는 0, string null, bool false 등). (생성자, 선언문 등). 이를 보완하기 위해 [OnDeserializing] (BEFORE BEFORE 직후라고 [OnDeserializing] ) 및 [OnDeserialized] (직후에 역 직렬화라고 [OnDeserialized] ) 속성을 [OnSerializing][OnSerialized] 와 함께 제공합니다.

Vector에 "Rating"을 추가하려고한다고 가정하고 값이 항상 1부터 시작하는지 확인하려고합니다. 아래에 쓰여진 방식대로, 역 직렬화 된 후에 0이됩니다.

[Serializable]
public class Vector
{
    public int X;
    public int Y;
    public int Z;

    [NonSerialized]
    public decimal Rating = 1M;

    public Vector()
    {
        Rating = 1M;
    }

    public Vector(decimal initialRating)
    {
        Rating = initialRating;
    }
}

이 문제를 해결하려면 클래스 내부에 다음 메소드를 추가하여 1로 설정하면됩니다.

[OnDeserializing]
void OnDeserializing(StreamingContext context)
{
    Rating = 1M;
}

또는 계산 된 값으로 설정하려는 경우 deserialize가 완료 될 때까지 기다린 다음 설정할 수 있습니다.

[OnDeserialized]
void OnDeserialized(StreamingContext context)
{
    Rating = 1 + ((X+Y+Z)/3);
}

비슷하게 [OnSerializing][OnSerialized] 를 사용하여 사물이 어떻게 쓰여지는지 제어 할 수 있습니다.

ISerializable을 구현하여 컨트롤 추가

그건 직렬화, 저장 및로드하는 방법에 대한 더 많은 컨트롤을 얻을 것이다

ISerializable 인터페이스를 구현하고 컴파일 할 빈 생성자를 만듭니다.

[Serializable]
public class Item : ISerializable
{
    private string _name;

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public Item ()
    {

    }

    protected Item (SerializationInfo info, StreamingContext context)
    {
        _name = (string)info.GetValue("_name", typeof(string));
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("_name", _name, typeof(string));
    }
}

데이터 직렬화의 경우 원하는 이름과 원하는 유형을 지정할 수 있습니다

info.AddValue("_name", _name, typeof(string));

데이터가 deserialize되면 원하는 유형을 읽을 수 있습니다.

_name = (string)info.GetValue("_name", typeof(string));

직렬화 대리 (ISerializationSurrogate 구현)

한 객체가 다른 객체의 직렬화 및 비 직렬화를 수행 할 수 있도록하는 직렬화 대리 대리자를 구현합니다.

또한 직렬화가 가능하지 않은 클래스를 적절하게 직렬화 또는 비 직렬화 할 수 있습니다.

ISerializationSurrogate 인터페이스 구현

public class ItemSurrogate : ISerializationSurrogate
{
    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        var item = (Item)obj;
        info.AddValue("_name", item.Name);
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {
        var item = (Item)obj;
        item.Name = (string)info.GetValue("_name", typeof(string));
        return item;
    }
}

그런 다음 SurrogateSelector를 정의하고 초기화하고이를 IFormatter에 할당하여 대리자에 대해 IFormatter에 알릴 필요가 있습니다

var surrogateSelector = new SurrogateSelector();
surrogateSelector.AddSurrogate(typeof(Item), new StreamingContext(StreamingContextStates.All), new ItemSurrogate());    
var binaryFormatter = new BinaryFormatter
{
    SurrogateSelector = surrogateSelector
};

클래스가 직렬화 가능으로 표시되지 않은 경우에도 마찬가지입니다.

//this class is not serializable
public class Item
{
    private string _name;

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}

완벽한 솔루션

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

namespace BinarySerializationExample
{
    class Item
    {
        private string _name;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
    }

    class ItemSurrogate : ISerializationSurrogate
    {
        public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
        {
            var item = (Item)obj;
            info.AddValue("_name", item.Name);
        }

        public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
        {
            var item = (Item)obj;
            item.Name = (string)info.GetValue("_name", typeof(string));
            return item;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var item = new Item
            {
                Name = "Orange"
            };

            var bytes = SerializeData(item);
            var deserializedData = (Item)DeserializeData(bytes);
        }

        private static byte[] SerializeData(object obj)
        {
            var surrogateSelector = new SurrogateSelector();
            surrogateSelector.AddSurrogate(typeof(Item), new StreamingContext(StreamingContextStates.All), new ItemSurrogate());

            var binaryFormatter = new BinaryFormatter
            {
                SurrogateSelector = surrogateSelector
            };

            using (var memoryStream = new MemoryStream())
            {
                binaryFormatter.Serialize(memoryStream, obj);
                return memoryStream.ToArray();
            }
        }

        private static object DeserializeData(byte[] bytes)
        {
            var surrogateSelector = new SurrogateSelector();
            surrogateSelector.AddSurrogate(typeof(Item), new StreamingContext(StreamingContextStates.All), new ItemSurrogate());

            var binaryFormatter = new BinaryFormatter
            {
                SurrogateSelector = surrogateSelector
            };

            using (var memoryStream = new MemoryStream(bytes))
                return binaryFormatter.Deserialize(memoryStream);
        }
    }
}

순차 화 바인더

바인더는 응용 프로그램 도메인에 어떤 유형의로드되고 있는지 검사 할 수있는 기회를 제공합니다

SerializationBinder에서 상속받은 클래스 만들기

class MyBinder : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        if (typeName.Equals("BinarySerializationExample.Item"))
            return typeof(Item);
        return null;
    }
}

이제 어떤 유형이로드되고 있는지를 확인할 수 있고이 기준에 따라 실제로 받고 싶은 것을 결정할 수 있습니다.

바인더를 사용하려면 BinaryFormatter에 추가해야합니다.

object DeserializeData(byte[] bytes)
{
    var binaryFormatter = new BinaryFormatter();
    binaryFormatter.Binder = new MyBinder();

    using (var memoryStream = new MemoryStream(bytes))
        return binaryFormatter.Deserialize(memoryStream);
}

완벽한 솔루션

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

namespace BinarySerializationExample
{
    class MyBinder : SerializationBinder
    {
        public override Type BindToType(string assemblyName, string typeName)
        {
            if (typeName.Equals("BinarySerializationExample.Item"))
                return typeof(Item);
            return null;
        }
    }

    [Serializable]
    public class Item
    {
        private string _name;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var item = new Item
            {
                Name = "Orange"
            };

            var bytes = SerializeData(item);    
            var deserializedData = (Item)DeserializeData(bytes);
        }

        private static byte[] SerializeData(object obj)
        {
            var binaryFormatter = new BinaryFormatter();
            using (var memoryStream = new MemoryStream())
            {
                binaryFormatter.Serialize(memoryStream, obj);
                return memoryStream.ToArray();
            }
        }

        private static object DeserializeData(byte[] bytes)
        {
            var binaryFormatter = new BinaryFormatter
            {
                Binder = new MyBinder()
            };

            using (var memoryStream = new MemoryStream(bytes))
                return binaryFormatter.Deserialize(memoryStream);
        }
    }
}

이전 버전과의 호환성 문제

이 작은 예제는 사전에주의를 기울이지 않으면 프로그램에서 이전 버전과의 호환성을 잃을 수있는 방법을 보여줍니다. 직렬화 프로세스를보다 효율적으로 제어 할 수있는 방법

처음에는 프로그램의 첫 번째 버전에 대한 예제를 작성합니다.

버전 1

[Serializable]
class Data
{
    [OptionalField]
    private int _version;
    
    public int Version
    {
        get { return _version; }
        set { _version = value; }
    }
}

이제 프로그램의 두 번째 버전에 새로운 클래스가 추가되었다고 가정 해 봅시다. 그리고 그것을 배열에 저장해야합니다.

이제 코드는 다음과 같습니다.

버전 2

[Serializable]
class NewItem
{
    [OptionalField]
    private string _name;

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}

[Serializable]
class Data
{
    [OptionalField]
    private int _version;

    public int Version
    {
        get { return _version; }
        set { _version = value; }
    }

    [OptionalField]
    private List<NewItem> _newItems;

    public List<NewItem> NewItems
    {
        get { return _newItems; }
        set { _newItems = value; }
    }
}

직렬화 및 비 직렬화를위한 코드

private static byte[] SerializeData(object obj)
{
    var binaryFormatter = new BinaryFormatter();
    using (var memoryStream = new MemoryStream())
    {
        binaryFormatter.Serialize(memoryStream, obj);
        return memoryStream.ToArray();
    }
}

private static object DeserializeData(byte[] bytes)
{
    var binaryFormatter = new BinaryFormatter();
    using (var memoryStream = new MemoryStream(bytes))
        return binaryFormatter.Deserialize(memoryStream);
}

그래서 v2의 프로그램에서 데이터를 직렬화하고 v1의 프로그램에서 직렬화를 시도하면 어떻게 될까요?

예외가 발생했습니다.

System.Runtime.Serialization.SerializationException was unhandled
Message=The ObjectManager found an invalid number of fixups. This usually indicates a problem in the Formatter.Source=mscorlib
StackTrace:
   at System.Runtime.Serialization.ObjectManager.DoFixups()
   at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream)
   at Microsoft.Samples.TestV1.Main(String[] args) in c:\Users\andrew\Documents\Visual Studio 2013\Projects\vts\CS\V1 Application\TestV1Part2\TestV1Part2.cs:line 29
   at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

왜?

ObjectManager에는 배열 및 참조 및 값 유형에 대한 종속성을 해결하는 다른 논리가 있습니다. 어셈블리에없는 새로운 참조 유형을 추가했습니다.

ObjectManager가 종속성을 해결하려고 시도하면 그래프가 작성됩니다. 배열을 보면 즉시 수정할 수 없으므로 나중에 더미 참조를 만든 다음 나중에 배열을 수정합니다.

그리고이 유형은 어셈블리에 없기 때문에 종속성을 수정할 수 없습니다. 어떤 이유로 요소는 수정 사항의 요소 목록에서 배열을 제거하지 못하며 결국에는 "IncorrectNumberOfFixups"예외를 발생시킵니다.

직렬화가 진행되는 과정에서 '문제'가 있습니다. 어떤 이유로 새로운 참조 유형의 배열에 대해서만 올바르게 작동하지 않습니다.

A Note:
Similar code will work correctly if you do not use arrays with new classes

문제를 수정하고 호환성을 유지하는 첫 번째 방법은 무엇입니까?

  • 클래스가 아닌 새로운 구조의 콜렉션을 사용하거나 사전 (가능한 클래스)을 사용하십시오. 사전은 keyvalue 쌍의 콜렉션이기 때문에 (구조입니다)
  • 이전 코드를 변경할 수없는 경우 ISerializable을 사용하십시오.


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow