Click here to Skip to main content
15,896,453 members
Articles / Programming Languages / C#

UniversalSerializer

Rate me:
Please Sign up or sign in to vote.
4.97/5 (108 votes)
15 Apr 2018Ms-RL31 min read 264.3K   4K   299  
An universal and easy serialization library for .NET and .NET Core.

// Copyright Christophe Bertrand.

// This class is very special: it tries to build & contain a class that will be constructed with the values of its own (private or internal) fields.
// That is risky, but it can help a lot with classes with no no-param (default) constructors.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
#if DEBUG
using System.Diagnostics;
#endif

namespace UniversalSerializerLib2
{
	/// <summary>
	/// For classes not serializable by the other ITypeContainers.
	/// It tries to call the class parameter constructors with the fields as parameters.
	/// </summary>
	internal static class ParametricConstructorsManager
	{

		/// <summary>
		/// A list of valid types.
		/// IntelligentConstructorContainer can contain these types safely.
		/// String format is 'Type'.AssemblyQualifiedName .
		/// </summary>
		internal static readonly string[] ValidatedTypes = new string[] { };

		/// <summary>
		/// A list of invalid types.
		/// IntelligentConstructorContainer can not contain these types safely.
		/// String format is 'Type'.AssemblyQualifiedName .
		/// </summary>
		internal static readonly string[] InvalidatedTypes = new string[] { };

		static readonly Type[] _InvalidatedTypes;

		static ParametricConstructorsManager()
		{
			_InvalidatedTypes = new Type[InvalidatedTypes.Length];
			for (int i = 0; i < InvalidatedTypes.Length; i++)
				_InvalidatedTypes[i] = Type.GetType(InvalidatedTypes[i]);
		}

		internal static object[] GetParameterFields(object o, ParametricConstructorDescriptor d)
		{
			var cons = d.constructorInfo;
			var fields = d.ParameterFields;
			var parameters = cons.GetParameters();
			object[] ret = new object[parameters.Length];

			for (int i = 0; i < parameters.Length; i++)
			{
				var field = fields[i];
				ret[i] = field.GetValue(o);
			}

			return ret;
		}

		/// <summary>
		/// Get a parametric constructor for this type.
		/// If none applies, returns null.
		/// </summary>
		static internal ParametricConstructorDescriptor GetTypeParamDescriptorFromCache(Type type)
		{
			Contains<ParametricConstructorDescriptor> d;
			if (paramDescriptorCache.TryGetValue(type, out d))
				return d.Value; // can return null.


			{
				// We need to analyse the class type:
				d = AnalyseClassType(type);
			}
			paramDescriptorCache.Add(type, d);

			return d.Value;
		}

		static Dictionary<Type, Contains<ParametricConstructorDescriptor>> paramDescriptorCache =
	new Dictionary<Type, Contains<ParametricConstructorDescriptor>>();

		// ---------------------------
		// ---------------------------
		// ---------------------------
		// ---------------------------

		#region analysis logic
		/// <summary>
		/// Returns Value=null if this type does not apply.
		/// </summary>
		static Contains<ParametricConstructorDescriptor> AnalyseClassType(Type t)//, out ConstructorInfo cons)
		{

			if (!t.IsEnum && !_InvalidatedTypes.Contains(t))
			{
				bool AvoidDefaultConstructor = true; // Never uses a default constructor.

				BindingFlags[] bflags = new BindingFlags[2] { BindingFlags.Public | BindingFlags.Instance, BindingFlags.NonPublic | BindingFlags.Instance };

				// 2 passes: one with public constructors, and one with private constructors.
				for (int iflags = 0; iflags < 2; iflags++)
				{
					var cs = t.GetConstructors(bflags[iflags]).Where((ci) => AvoidDefaultConstructor ? ci.GetParameters().Length != 0 : true);
					ConstructorInfo cons;
					var mf = AnalyseConstructors(cs, out cons);
					if (mf != null)
						return ParametricConstructorDescriptor.CreateDirectTypeParamDescriptor(cons, mf.ToArray());
				}
			}

			return new Contains<ParametricConstructorDescriptor>(null); // This type does not apply.
		}

		// ------------------------------


		static List<FieldInfo> AnalyseConstructors(
			IEnumerable<ConstructorInfo> constructors,
			out ConstructorInfo FoundConstructor)
		{
			// Sort the list from much parameters to none:
			var sorted = new List<ConstructorInfo>(constructors);
			if (sorted.Count > 1)
				sorted.Sort((a, b) => b.GetParameters().Length - a.GetParameters().Length);
			foreach (var cons in sorted)
			{
				var mf = AnalyseConstructor(cons);
				if (mf != null)
				{
					FoundConstructor = cons;
					return mf;
				}
			}
			FoundConstructor = null;
			return null;
		}

		/// <summary>
		/// Try to find a field for each parameter of the constructor.
		/// </summary>
		/// <param name="constructor"></param>
		static List<FieldInfo> AnalyseConstructor(ConstructorInfo constructor)
		{
			var pars = constructor.GetParameters();
			var availableFields = new List<FieldInfo>(constructor.DeclaringType.GetFields(
				BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance));
			List<FieldInfo> MatchingFields = new List<FieldInfo>(availableFields.Count);

			foreach (var par in pars)
			{
				FieldInfo field = FindMatchingField(par, availableFields);
				if (field == null)
					return null; // If only one param is not found, we abandon.
				MatchingFields.Add(field);
				availableFields.Remove(field); // no more available.
			}
			return MatchingFields;
		}

		static FieldInfo FindMatchingField(ParameterInfo par, List<FieldInfo> availableFields)
		{
			string paramName = par.Name;
			Type paramType = par.ParameterType;

			foreach (var field in availableFields)
			{
				if (paramType.Is(field.FieldType))
				{
					if (paramName == field.Name) // parameter and field have the same name.
						return field;
					if (field.IsPrivate)
					{
						string privname = "_" + paramName; // Ex: "MyField" -> "_MyField".
						if (privname == field.Name)
							return field;
						privname = "_" + paramName.ToLower()[0] + paramName.Substring(1); // Ex: "MyField" -> "_myField".
						if (privname == field.Name)
							return field;
					}
					string privname3 = paramName.ToUpper()[0] + paramName.Substring(1); // Ex: "myField" -> "MyField".
					if (privname3 == field.Name)
						return field;
				}
			}
			return null;
		}
		#endregion analysis logic

		// ###################################################################################
		// ###################################################################################
		// ###################################################################################
		// ###################################################################################

		internal class Contains<T> where T : class
		{
			internal T Value;

			internal Contains(T value)
			{
				this.Value = value;
			}
		}

	}

	// ###################################################################################
	// ###################################################################################


	/// <summary>
	/// One instance per Type.
	/// That will NOT be serialized.
	/// </summary>
	internal class ParametricConstructorDescriptor
	{
		internal readonly FieldInfo[] ParameterFields;
		//internal readonly int[] FieldIndexes;
		internal readonly ConstructorInfo constructorInfo;

		// ------------------------------

		/// <summary>
		/// Warning: You have to check GetTypeParamDescriptor first.
		/// </summary>
		ParametricConstructorDescriptor(
			FieldInfo[] ParameterFields,
			//int[] FieldIndexes,
			ConstructorInfo constructorInfo
			)
		{
			this.constructorInfo = constructorInfo;
			this.ParameterFields = ParameterFields;
		}

		// ------------------------------

		/// <summary>
		/// Warning: You have to check GetTypeParamDescriptor first.
		/// </summary>
		static internal ParametricConstructorsManager.Contains<ParametricConstructorDescriptor> CreateDirectTypeParamDescriptor(
			ConstructorInfo constructorInfo, FieldInfo[] Fields)
		{
			return new ParametricConstructorsManager.Contains<ParametricConstructorDescriptor>(
				new ParametricConstructorDescriptor(Fields, constructorInfo));
		}

		// ------------------------------

		/// <summary>
		/// For each parameter, find its index in the SelectedFields, in the order of this.ParameterFields.
		/// </summary>
		/// <param name="SelectedFields">Concatenated selected public and private fields of a particular TypeManager.</param>
		/// <returns>int[this.ParameterFields.Length]</returns>
		internal int[] GetParameterIndexes(FieldInfo[] SelectedFields)
		{
			int[] indexes = new int[this.ParameterFields.Length];
			for (int i = 0; i < this.ParameterFields.Length; i++)
			{
				var fi = ParameterFields[i];

				indexes[i] = Array.IndexOf(SelectedFields, fi);
#if DEBUG
				if (indexes[i] < 0)
					throw new Exception();
#endif
			}
			return indexes;
		}

		// ------------------------------
		// ------------------------------
	}

	// ###################################################################################
	// ###################################################################################
	// ###################################################################################
	// ###################################################################################

}

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 Microsoft Reciprocal License


Written By
Software Developer (Senior) independent
France France
Hi !

I made my first program on a Sinclair ZX-81.
Since that day, I have the virus of computers. Smile | :)

Here is my website:
https://chrisbertrand.net

And my blog:
https://chrisbertrandprogramer.wordpress.com/

Comments and Discussions