Skip to content

Instantly share code, notes, and snippets.

@conwid
Last active May 16, 2023 14:37
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save conwid/cb9284584c9204746856d89b2ad3498a to your computer and use it in GitHub Desktop.
Save conwid/cb9284584c9204746856d89b2ad3498a to your computer and use it in GitHub Desktop.
More complete IFormatter implementation reference for students preparing for MS 70-483
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