Click here to Skip to main content
15,886,026 members
Articles / Programming Languages / C#

NetSerializer - A Fast, Simple Serializer for .NET

Rate me:
Please Sign up or sign in to vote.
4.87/5 (29 votes)
14 Sep 2012MPL13 min read 96.8K   107  
NetSerializer - A fast, simple serializer for .NET
/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

//#define GENERATE_DEBUGGING_ASSEMBLY

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Diagnostics;

namespace NetSerializer
{
	public static partial class Serializer
	{
		static Dictionary<Type, ushort> s_typeIDMap;

		delegate void SerializerSwitch(Stream stream, object ob);
		delegate void DeserializerSwitch(Stream stream, out object ob);

		static SerializerSwitch s_serializerSwitch;
		static DeserializerSwitch s_deserializerSwitch;

		static bool s_initialized;

		public static void Initialize(Type[] rootTypes)
		{
			if (s_initialized)
				throw new InvalidOperationException("NetSerializer already initialized");

			var types = CollectTypes(rootTypes);

			GenerateAssembly(types);

			s_typeIDMap = GenerateDynamic(types);

			s_initialized = true;
		}

		public static void Serialize(Stream stream, object data)
		{
			if (!s_initialized)
				throw new InvalidOperationException("NetSerializer not initialized");

			D("Serializing {0}", data.GetType().Name);

			s_serializerSwitch(stream, data);
		}

		public static object Deserialize(Stream stream)
		{
			if (!s_initialized)
				throw new InvalidOperationException("NetSerializer not initialized");

			D("Deserializing");

			object o;
			s_deserializerSwitch(stream, out o);
			return o;
		}

		[System.Diagnostics.Conditional("DEBUG")]
		static void D(string fmt, params object[] args)
		{
			//Console.WriteLine("S: " + String.Format(fmt, args));
		}

		[System.Diagnostics.Conditional("DEBUG")]
		static void D(ILGenerator ilGen, string fmt, params object[] args)
		{
			//ilGen.EmitWriteLine("E: " + String.Format(fmt, args));
		}

		static void CollectTypes(Type type, HashSet<Type> typeSet)
		{
			if (typeSet.Contains(type))
				return;

			if (type.IsAbstract)
				return;

			if (type.IsInterface)
				return;

			if (!type.IsSerializable)
				throw new NotSupportedException(String.Format("Type {0} is not marked as Serializable", type.FullName));

			typeSet.Add(type);

			if (type.IsArray)
			{
				CollectTypes(type.GetElementType(), typeSet);
			}
			else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
			{
				var args = type.GetGenericArguments();

				Debug.Assert(args.Length == 2);

				// Dictionary<K,V> is stored as KeyValuePair<K,V>[]

				var arrayType = typeof(KeyValuePair<,>).MakeGenericType(args).MakeArrayType();

				CollectTypes(arrayType, typeSet);
			}
			else
			{
				var fields = GetFieldInfos(type);

				foreach (var field in fields)
					CollectTypes(field.FieldType, typeSet);
			}
		}

		static Type[] CollectTypes(Type[] rootTypes)
		{
			var primitives = new Type[] {
				typeof(bool),
				typeof(byte), typeof(sbyte),
				typeof(char),
				typeof(ushort), typeof(short),
				typeof(uint), typeof(int),
				typeof(ulong), typeof(long),
				typeof(float), typeof(double),
				typeof(string),
			};

			var typeSet = new HashSet<Type>(primitives);

			foreach (var type in rootTypes)
				CollectTypes(type, typeSet);

			return typeSet
				.OrderBy(t => t.FullName, StringComparer.Ordinal)
				.ToArray();
		}

		static Dictionary<Type, TypeData> GenerateTypeData(Type[] types)
		{
			var map = new Dictionary<Type, TypeData>(types.Length);

			// TypeID 0 is reserved for null
			ushort typeID = 1;
			foreach (var type in types)
			{
				var writer = Primitives.GetWritePrimitive(type);
				var reader = Primitives.GetReadPrimitive(type);

				if ((writer != null) != (reader != null))
					throw new InvalidOperationException(String.Format("Missing a read or write primitive for {0}", type.FullName));

				var isStatic = writer != null;

				if (type.IsPrimitive && isStatic == false)
					throw new InvalidOperationException(String.Format("Missing primitive read/write methods for {0}", type.FullName));

				var td = new TypeData(typeID++);

				if (isStatic)
				{
					td.WriterMethodInfo = writer;
					td.ReaderMethodInfo = reader;
					td.IsDynamic = false;
				}
				else
				{
					if (typeof(System.Runtime.Serialization.ISerializable).IsAssignableFrom(type))
						throw new InvalidOperationException(String.Format("Cannot serialize {0}: ISerializable not supported", type.FullName));

					td.IsDynamic = true;
				}

				map[type] = td;
			}

			return map;
		}

		static Dictionary<Type, ushort> GenerateDynamic(Type[] types)
		{
			Dictionary<Type, TypeData> map = GenerateTypeData(types);

			var nonStaticTypes = map.Where(kvp => kvp.Value.IsDynamic).Select(kvp => kvp.Key);

			/* generate stubs */
			foreach (var type in nonStaticTypes)
			{
				var dm = GenerateDynamicSerializerStub(type);
				map[type].WriterMethodInfo = dm;
				map[type].WriterILGen = dm.GetILGenerator();
			}

			foreach (var type in nonStaticTypes)
			{
				var dm = GenerateDynamicDeserializerStub(type);
				map[type].ReaderMethodInfo = dm;
				map[type].ReaderILGen = dm.GetILGenerator();
			}

			var serializerSwitchMethod = new DynamicMethod("SerializerSwitch", null,
				new Type[] { typeof(Stream), typeof(object) },
				typeof(Serializer), true);
			serializerSwitchMethod.DefineParameter(1, ParameterAttributes.None, "stream");
			serializerSwitchMethod.DefineParameter(2, ParameterAttributes.None, "value");
			var serializerSwitchMethodInfo = serializerSwitchMethod;

			var deserializerSwitchMethod = new DynamicMethod("DeserializerSwitch", null,
				new Type[] { typeof(Stream), typeof(object).MakeByRefType() },
				typeof(Serializer), true);
			deserializerSwitchMethod.DefineParameter(1, ParameterAttributes.None, "stream");
			deserializerSwitchMethod.DefineParameter(2, ParameterAttributes.Out, "value");
			var deserializerSwitchMethodInfo = deserializerSwitchMethod;

			var ctx = new CodeGenContext(map, serializerSwitchMethodInfo, deserializerSwitchMethodInfo);

			/* generate bodies */
			foreach (var type in nonStaticTypes)
				GenerateSerializerBody(ctx, type, map[type].WriterILGen);

			foreach (var type in nonStaticTypes)
				GenerateDeserializerBody(ctx, type, map[type].ReaderILGen);

			var ilGen = serializerSwitchMethod.GetILGenerator();
			GenerateSerializerSwitch(ctx, ilGen, map);
			s_serializerSwitch = (SerializerSwitch)serializerSwitchMethod.CreateDelegate(typeof(SerializerSwitch));

			ilGen = deserializerSwitchMethod.GetILGenerator();
			GenerateDeserializerSwitch(ctx, ilGen, map);
			s_deserializerSwitch = (DeserializerSwitch)deserializerSwitchMethod.CreateDelegate(typeof(DeserializerSwitch));

			return map.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.TypeID);
		}

		[System.Diagnostics.Conditional("GENERATE_DEBUGGING_ASSEMBLY")]
		static void GenerateAssembly(Type[] types)
		{
			Dictionary<Type, TypeData> map = GenerateTypeData(types);

			var nonStaticTypes = map.Where(kvp => kvp.Value.IsDynamic).Select(kvp => kvp.Key);

			var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("NetSerializerDebug"), AssemblyBuilderAccess.RunAndSave);
			var modb = ab.DefineDynamicModule("NetSerializerDebug.dll");
			var tb = modb.DefineType("NetSerializer", TypeAttributes.Public);

			/* generate stubs */
			foreach (var type in nonStaticTypes)
			{
				var mb = GenerateStaticSerializerStub(tb, type);
				map[type].WriterMethodInfo = mb;
				map[type].WriterILGen = mb.GetILGenerator();
			}

			foreach (var type in nonStaticTypes)
			{
				var dm = GenerateStaticDeserializerStub(tb, type);
				map[type].ReaderMethodInfo = dm;
				map[type].ReaderILGen = dm.GetILGenerator();
			}

			var serializerSwitchMethod = tb.DefineMethod("SerializerSwitch", MethodAttributes.Public | MethodAttributes.Static, null, new Type[] { typeof(Stream), typeof(object) });
			serializerSwitchMethod.DefineParameter(1, ParameterAttributes.None, "stream");
			serializerSwitchMethod.DefineParameter(2, ParameterAttributes.None, "value");
			var serializerSwitchMethodInfo = serializerSwitchMethod;

			var deserializerSwitchMethod = tb.DefineMethod("DeserializerSwitch", MethodAttributes.Public | MethodAttributes.Static, null, new Type[] { typeof(Stream), typeof(object).MakeByRefType() });
			deserializerSwitchMethod.DefineParameter(1, ParameterAttributes.None, "stream");
			deserializerSwitchMethod.DefineParameter(2, ParameterAttributes.Out, "value");
			var deserializerSwitchMethodInfo = deserializerSwitchMethod;

			var ctx = new CodeGenContext(map, serializerSwitchMethodInfo, deserializerSwitchMethodInfo);

			/* generate bodies */
			foreach (var type in nonStaticTypes)
				GenerateSerializerBody(ctx, type, map[type].WriterILGen);

			foreach (var type in nonStaticTypes)
				GenerateDeserializerBody(ctx, type, map[type].ReaderILGen);

			var ilGen = serializerSwitchMethod.GetILGenerator();
			GenerateSerializerSwitch(ctx, ilGen, map);

			ilGen = deserializerSwitchMethod.GetILGenerator();
			GenerateDeserializerSwitch(ctx, ilGen, map);

			tb.CreateType();
			ab.Save("NetSerializerDebug.dll");
		}

		/* called from the dynamically generated code */
		static ushort GetTypeID(object ob)
		{
			ushort id;

			if (ob == null)
				return 0;

			if (s_typeIDMap.TryGetValue(ob.GetType(), out id) == false)
				throw new InvalidOperationException(String.Format("Unknown type {0}", ob.GetType().FullName));

			return id;
		}

		static IEnumerable<FieldInfo> GetFieldInfos(Type type)
		{
			Debug.Assert(type.IsSerializable);

			var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)
				.Where(fi => (fi.Attributes & FieldAttributes.NotSerialized) == 0)
				.OrderBy(f => f.Name, StringComparer.Ordinal);

			if (type.BaseType == null)
			{
				return fields;
			}
			else
			{
				var baseFields = GetFieldInfos(type.BaseType);
				return baseFields.Concat(fields);
			}
		}

		sealed class CodeGenContext
		{
			readonly Dictionary<Type, TypeData> m_typeMap;

			public CodeGenContext(Dictionary<Type, TypeData> typeMap, MethodInfo serializerSwitch, MethodInfo deserializerSwitch)
			{
				m_typeMap = typeMap;
				this.SerializerSwitchMethodInfo = serializerSwitch;
				this.DeserializerSwitchMethodInfo = deserializerSwitch;
			}

			public MethodInfo SerializerSwitchMethodInfo { get; private set; }
			public MethodInfo DeserializerSwitchMethodInfo { get; private set; }

			public MethodInfo GetWriterMethodInfo(Type type)
			{
				return m_typeMap[type].WriterMethodInfo;
			}

			public ILGenerator GetWriterILGen(Type type)
			{
				return m_typeMap[type].WriterILGen;
			}

			public MethodInfo GetReaderMethodInfo(Type type)
			{
				return m_typeMap[type].ReaderMethodInfo;
			}

			public ILGenerator GetReaderILGen(Type type)
			{
				return m_typeMap[type].ReaderILGen;
			}

			public bool IsDynamic(Type type)
			{
				return m_typeMap[type].IsDynamic;
			}
		}

		sealed class TypeData
		{
			public TypeData(ushort typeID)
			{
				this.TypeID = typeID;
			}

			public readonly ushort TypeID;
			public bool IsDynamic;
			public MethodInfo WriterMethodInfo;
			public ILGenerator WriterILGen;
			public MethodInfo ReaderMethodInfo;
			public ILGenerator ReaderILGen;
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Mozilla Public License 1.1 (MPL 1.1)


Written By
Finland Finland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions