C# Language
이진 직렬화
수색…
비고
이진 직렬화 엔진은 .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
, Z
및 Name
이 모두 serialize됩니다.
[NonSerialized]
또는 [OptionalField]
로 표시하지 않는 한 모든 멤버가 비 직렬화에 있어야합니다. 이 예에서는 X
, Y
및 Z
가 모두 필요하며 스트림에 스트림이 없으면 비 직렬화가 실패합니다. 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을 사용하십시오.