More complete IFormatter implementation reference for students preparing for MS 70-483
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class IniFormatter : IFormatter | |
{ | |
public class IniTypeBinder : SerializationBinder | |
{ | |
public override Type BindToType(string assemblyName, string typeName) => Type.GetType(typeName.Split('=')[1]); | |
public override void BindToName(Type serializedType, out string assemblyName, out string typeName) | |
{ | |
assemblyName = $"{IniFormatter.AssemblyNameKey}={serializedType.Assembly.FullName}"; | |
typeName = $"{IniFormatter.ClassNameKey}={serializedType.AssemblyQualifiedName}"; | |
} | |
} | |
public ISurrogateSelector SurrogateSelector { get; set; } | |
public StreamingContext Context { get; set; } | |
private IniTypeBinder serializationBinder; | |
public SerializationBinder Binder | |
{ | |
get => serializationBinder; | |
set | |
{ | |
if (!(value is IniTypeBinder iniTypeBinder)) | |
throw new NotSupportedException("Sorry, only IniTypeBinder implementations are supported"); | |
serializationBinder = (IniTypeBinder)value; | |
} | |
} | |
private const string ClassNameKey = "@Class"; | |
private const string AssemblyNameKey = "@Assembly"; | |
public IniFormatter() | |
{ | |
Context = new StreamingContext(StreamingContextStates.All); | |
Binder = new IniTypeBinder(); | |
} | |
#region serialization | |
public void Serialize(Stream serializationStream, object graph) | |
{ | |
var objectType = graph.GetType(); | |
var serializationSurrogate = SurrogateSelector?.GetSurrogate(objectType, Context, out var _); | |
if (serializationSurrogate != null) | |
SerializeWithSurrogate(serializationStream, graph, objectType, serializationSurrogate); | |
else if (graph is ISerializable serializable) | |
SerializeAsISerializable(serializationStream, graph, objectType, serializable); | |
else | |
SerializeWithFormatterServices(serializationStream, graph, objectType); | |
GetCallbackDelegate(objectType, typeof(OnSerializedAttribute))?.DynamicInvoke(graph, Context); | |
} | |
private void SerializeWithFormatterServices(Stream serializationStream, object graph, Type objectType) | |
{ | |
if (!objectType.IsSerializable) | |
throw new SerializationException($"Type {objectType} is not serializable"); | |
var members = FormatterServices.GetSerializableMembers(objectType, this.Context); | |
var memberData = FormatterServices.GetObjectData(graph, members); | |
GetCallbackDelegate(objectType, typeof(OnSerializingAttribute))?.DynamicInvoke(graph, Context); | |
using (var sw = new StreamWriter(serializationStream)) | |
{ | |
WriteTypeName(objectType, sw); | |
foreach (var m in members) | |
{ | |
sw.WriteLine($"{m.Name}={m.ToString()}"); | |
} | |
} | |
} | |
private void WriteTypeName(Type objectType, StreamWriter sw) | |
{ | |
Binder.BindToName(objectType, out var assemblyName, out var typeName); | |
sw.WriteLine(typeName); | |
sw.WriteLine(assemblyName); | |
} | |
private void SerializeAsISerializable(Stream serializationStream, object graph, Type objectType, ISerializable serializable) | |
{ | |
if (!objectType.IsSerializable) | |
throw new SerializationException($"Type {objectType} is not serializable"); | |
var serializationInfo = new SerializationInfo(objectType, new FormatterConverter()); | |
serializable.GetObjectData(serializationInfo, Context); | |
SerializeFromSerializationInfo(serializationStream, graph, serializationInfo); | |
} | |
private void SerializeWithSurrogate(Stream serializationStream, object graph, Type objectType, ISerializationSurrogate serializationSurrogate) | |
{ | |
var serializationInfo = new SerializationInfo(objectType, new FormatterConverter()); | |
serializationSurrogate.GetObjectData(graph, serializationInfo, Context); | |
SerializeFromSerializationInfo(serializationStream, graph, serializationInfo); | |
} | |
private Delegate GetCallbackDelegate(Type objectType, Type methodAttribute) | |
{ | |
var onSerializingMethod = objectType.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) | |
.SingleOrDefault(m => m.GetCustomAttribute<OnSerializingAttribute>() != null); | |
if (onSerializingMethod == null) | |
return null; | |
if (!(onSerializingMethod.ReturnType == typeof(void) && | |
onSerializingMethod.GetParameters().Length == 1 && | |
onSerializingMethod.GetParameters()[0].ParameterType == typeof(StreamingContext)) && | |
!onSerializingMethod.IsVirtual) | |
throw new InvalidOperationException($"Method {onSerializingMethod.Name} found with {methodAttribute}, but method is not compliant with the requirements of this attribute"); | |
var funcType = typeof(Action<,>).MakeGenericType(objectType, typeof(StreamingContext)); | |
return Delegate.CreateDelegate(funcType, onSerializingMethod); | |
} | |
private void SerializeFromSerializationInfo(Stream serializationStream, object graph, SerializationInfo serializationInfo) | |
{ | |
GetCallbackDelegate(graph.GetType(), typeof(OnSerializingAttribute))?.DynamicInvoke(graph, Context); | |
using (var sw = new StreamWriter(serializationStream)) | |
{ | |
WriteTypeName(graph.GetType(), sw); | |
foreach (var item in serializationInfo) | |
{ | |
sw.WriteLine($"{item.Name}={item.Value.ToString()}"); | |
} | |
} | |
} | |
#endregion | |
#region deserialization | |
public object Deserialize(Stream serializationStream) | |
{ | |
using (var sr = new StreamReader(serializationStream)) | |
{ | |
var typeInfo = sr.ReadLine(); | |
var assemblyInfo = sr.ReadLine(); | |
Type objectType = Binder.BindToType(assemblyInfo, typeInfo); | |
var serializationSurrogate = SurrogateSelector?.GetSurrogate(objectType, Context, out var _); | |
if (serializationSurrogate != null) | |
return DeserializeWithSurrogate(sr, objectType, serializationSurrogate); | |
else if (typeof(ISerializable).IsAssignableFrom(objectType)) | |
return DeserializeAsISerializable(sr, objectType); | |
else | |
return DeserializeWithFormatterServices(sr, objectType); | |
} | |
} | |
private object DeserializeAsISerializable(StreamReader sr, Type objectType) | |
{ | |
throw new NotImplementedException(); | |
} | |
private object DeserializeWithSurrogate(StreamReader sr, Type objectType, ISerializationSurrogate serializationSurrogate) | |
{ | |
throw new NotImplementedException(); | |
} | |
private object DeserializeWithFormatterServices(StreamReader serializationReader, Type objectType) | |
{ | |
var result = FormatterServices.GetUninitializedObject(objectType); | |
var members = FormatterServices.GetSerializableMembers(objectType, this.Context); | |
var serializationData = new Dictionary<string, object>(); | |
while (!serializationReader.EndOfStream) | |
{ | |
var data = serializationReader.ReadLine(); | |
var splitData = data.Split('='); | |
serializationData.Add(splitData[0], splitData[1]); | |
} | |
var correctedTypes = new List<object>(members.Length); | |
for (int i = 0; i < members.Length; i++) | |
{ | |
var f = (FieldInfo)members[i]; | |
correctedTypes.Add(Convert.ChangeType(serializationData[f.Name], f.FieldType)); | |
} | |
FormatterServices.PopulateObjectMembers(result, members, correctedTypes.ToArray()); | |
return result; | |
} | |
#endregion | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment