/Rider/Utils/SilverlightSerializer.cs
C# | 1333 lines | 908 code | 134 blank | 291 comment | 241 complexity | 7edcf2e6f9c5bfd2896b9ba244465a87 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- // SilverlightSerializer by Mike Talbot
- // http://whydoidoit.com
- // email: mike.talbot@alterian.com
- // twitter: mike_talbot
- //
- // This code is free to use, no warranty is offered or implied.
- // If you redistribute, please retain this header.
- #region
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.IO;
- using System.Linq;
- using System.Reflection;
- #endregion
- namespace Rider.Utils
- {
- /// <summary>
- /// Indicates that a property or field should not be serialized
- /// </summary>
- [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
- public class DoNotSerialize : Attribute
- {
- }
- /// <summary>
- /// Used in checksum mode to flag a property as not being part
- /// of the "meaning" of an object - i.e. two objects with the
- /// same checksum "mean" the same thing, even if some of the
- /// properties are different, those properties would not be
- /// relevant to the purpose of the object
- /// </summary>
- [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
- public class DoNotChecksum : Attribute
- {
- }
- /// <summary>
- /// Attribute used to flag IDs this can be useful for check object
- /// consistence when the serializer is in a mode that does not
- /// serialize identifiers
- /// </summary>
- [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
- public class SerializerId : Attribute
- {
- }
- public interface ISerializeObject
- {
- object[] Serialize(object target);
- object Deserialize(object[] data);
- }
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
- public class SerializerAttribute : Attribute
- {
- internal Type SerializesType;
- public SerializerAttribute(Type serializesType)
- {
- SerializesType = serializesType;
- }
- }
- /// <summary>
- /// Silverlight/.NET compatible binary serializer with suppression support
- /// produces compact representations, suitable for further compression
- /// </summary>
- public static class SilverlightSerializer
- {
- private static readonly Dictionary<Type, IEnumerable<FieldInfo>> FieldLists = new Dictionary<Type, IEnumerable<FieldInfo>>();
- private static readonly Dictionary<string, IEnumerable<PropertyInfo>> PropertyLists = new Dictionary<string, IEnumerable<PropertyInfo>>();
- private static readonly Dictionary<string, IEnumerable<PropertyInfo>> ChecksumLists = new Dictionary<string, IEnumerable<PropertyInfo>>();
- [ThreadStatic]
- private static List<Type> _knownTypes;
- [ThreadStatic]
- private static Dictionary<object, int> _seenObjects;
- [ThreadStatic]
- private static List<object> _loadedObjects;
- [ThreadStatic]
- private static List<string> _propertyIds;
- [ThreadStatic]
- private static Stack<List<object>> _loStack;
- [ThreadStatic]
- private static Stack<Dictionary<object, int>> _soStack;
- [ThreadStatic]
- private static Stack<List<Type>> _ktStack;
- [ThreadStatic]
- private static Stack<List<string>> _piStack;
- [ThreadStatic]
- private static bool _isChecksum;
- [ThreadStatic]
- public static bool IgnoreIds;
- /// <summary>
- /// Arguments for a missing type event
- /// </summary>
- public class TypeMappingEventArgs : EventArgs
- {
- /// <summary>
- /// The missing types name
- /// </summary>
- public string TypeName = string.Empty;
- /// <summary>
- /// Supply a type to use instead
- /// </summary>
- public Type UseType = null;
- }
- /// <summary>
- /// Event that is fired if a particular type cannot be found
- /// </summary>
- public static event EventHandler<TypeMappingEventArgs> MapMissingType;
- private static void InvokeMapMissingType(TypeMappingEventArgs e)
- {
- EventHandler<TypeMappingEventArgs> handler = MapMissingType;
- if (handler != null)
- handler(null, e);
- }
- /// <summary>
- /// Put the serializer into Checksum mode
- /// </summary>
- public static bool IsChecksum
- {
- get
- {
- return _isChecksum;
- }
- set
- {
- _isChecksum = value;
- }
- }
- /// <summary>
- /// Deserialize to a type
- /// </summary>
- /// <param name="array"></param>
- /// <returns></returns>
- public static T Deserialize<T>(byte[] array) where T : class
- {
- return Deserialize(array) as T;
- }
- /// <summary>
- /// Deserialize from a stream to a type
- /// </summary>
- /// <param name="stream"></param>
- /// <returns></returns>
- public static T Deserialize<T>(Stream stream) where T : class
- {
- return Deserialize(stream) as T;
- }
- /// <summary>
- /// Get a checksum for an item. Checksums "should" be different
- /// for every object that has a different "meaning". You can
- /// flag properties as DoNotChecksum if that helps to keep decorative
- /// properties away from the checksum whilst including meaningful ones
- /// </summary>
- /// <param name="item">The object to checksum</param>
- /// <returns>A checksum string, this includes no illegal characters and can be used as a file name</returns>
- public static string GetChecksum(object item)
- {
- if (item == null)
- return "";
- byte[] checksum = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
- var isChecksum = SilverlightSerializer.IsChecksum;
- SilverlightSerializer.IsChecksum = true;
- var toBytes = SilverlightSerializer.Serialize(item);
- SilverlightSerializer.IsChecksum = isChecksum;
- for (var i = 0; i < toBytes.Length; i++)
- {
- checksum[i & 15] ^= toBytes[i];
- }
- return toBytes.Count().ToString() + Encode(checksum);
- }
- private static string Encode(byte[] checksum)
- {
- var s = Convert.ToBase64String(checksum);
- return s.Aggregate("", (current, c) => current + (Char.IsLetterOrDigit(c)
- ? c
- : Char.GetNumericValue(c)));
- }
- //Holds a reference to the custom serializers
- private static readonly Dictionary<Type, ISerializeObject> Serializers = new Dictionary<Type, ISerializeObject>();
- //Dictionary to ensure we only scan an assembly once
- private static readonly Dictionary<Assembly, bool> Assemblies = new Dictionary<Assembly, bool>();
- /// <summary>
- /// Register all of the custom serializers in an assembly
- /// </summary>
- /// <param name="assembly">Leave blank to register the assembly that the method is called from, or pass an assembly</param>
- public static void RegisterSerializationAssembly(Assembly assembly = null)
- {
- if (assembly == null)
- assembly = Assembly.GetCallingAssembly();
- if (Assemblies.ContainsKey(assembly))
- return;
- Assemblies[assembly] = true;
- ScanAllTypesForAttribute((tp, attr) =>
- {
- Serializers[((SerializerAttribute)attr).SerializesType] = Activator.CreateInstance(tp) as ISerializeObject;
- }, assembly, typeof(SerializerAttribute));
- }
- //Function to be called when scanning types
- internal delegate void ScanTypeFunction(Type type, Attribute attribute);
- /// <summary>
- /// Scan all of the types in an assembly for a particular attribute
- /// </summary>
- /// <param name="function">The function to call</param>
- /// <param name="assembly">The assembly to scan</param>
- /// <param name="attribute">The attribute to look for</param>
- internal static void ScanAllTypesForAttribute(ScanTypeFunction function, Assembly assembly, Type attribute = null)
- {
- try
- {
- foreach (var tp in assembly.GetTypes())
- {
- if (attribute != null)
- {
- var attrs = Attribute.GetCustomAttributes(tp, attribute, false);
- if (attrs != null)
- {
- foreach (var attr in attrs)
- function(tp, attr);
- }
- }
- else
- function(tp, null);
- }
- }
- catch (Exception)
- {
- }
- }
- /// <summary>
- /// Dictionary of all the used objects to check if properties are different
- /// to those set during construction
- /// </summary>
- private static readonly Dictionary<Type, object> Vanilla = new Dictionary<Type, object>();
- /// <summary>
- /// Write persistence debugging information to the debug output window
- /// often used with Verbose
- /// </summary>
- public static bool IsLoud;
- /// <summary>
- /// Write all types, even if they are known, often used with Loud mode
- /// </summary>
- public static bool Verbose;
- /// <summary>
- /// Caches and returns property info for a type
- /// </summary>
- /// <param name = "itm">The type that should have its property info returned</param>
- /// <returns>An enumeration of PropertyInfo objects</returns>
- /// <remarks>
- /// It should be noted that the implementation converts the enumeration returned from reflection to an array as this more than double the speed of subsequent reads
- /// </remarks>
- private static IEnumerable<PropertyInfo> GetPropertyInfo(Type itm)
- {
- lock (PropertyLists)
- {
- IEnumerable<PropertyInfo> ret = null;
- Debug.Assert(itm.AssemblyQualifiedName != null);
- if (!IsChecksum)
- {
- if (!PropertyLists.TryGetValue(itm.AssemblyQualifiedName, out ret))
- {
- ret = itm.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.GetCustomAttributes(typeof(DoNotSerialize), false).Count() == 0 && !(p.GetIndexParameters().Count() > 0) && (p.GetSetMethod() != null)).ToArray();
- PropertyLists[itm.AssemblyQualifiedName] = ret;
- }
- }
- else
- {
- if (!ChecksumLists.TryGetValue(itm.AssemblyQualifiedName, out ret))
- {
- ret = itm.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.GetCustomAttributes(typeof(DoNotSerialize), false).Count() == 0 && p.GetCustomAttributes(typeof(DoNotChecksum), true).Count() == 0 && !(p.GetIndexParameters().Count() > 0) && (p.GetSetMethod() != null)).ToArray();
- ChecksumLists[itm.AssemblyQualifiedName] = ret;
- }
- }
- return IgnoreIds && ret != null
- ? ret.Where(p => p.GetCustomAttributes(typeof(SerializerId), true).Count() == 0)
- : ret;
- }
- }
- /// <summary>
- /// Caches and returns field info for a type
- /// </summary>
- /// <param name = "itm">The type that should have its field info returned</param>
- /// <returns>An enumeration of FieldInfo objects</returns>
- /// <remarks>
- /// It should be noted that the implementation converts the enumeration returned from reflection to an array as this more than double the speed of subsequent reads
- /// </remarks>
- private static IEnumerable<FieldInfo> GetFieldInfo(Type itm)
- {
- lock (FieldLists)
- {
- IEnumerable<FieldInfo> ret = null;
- if (FieldLists.ContainsKey(itm))
- ret = FieldLists[itm];
- else
- {
- ret = FieldLists[itm] = itm.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetField).Where(p => p.GetCustomAttributes(typeof(DoNotSerialize), false).Count() == 0).ToArray();
- }
- return IsChecksum ? ret.Where(p => p.GetCustomAttributes(typeof(DoNotChecksum), true).Count() == 0) : ret;
- }
- }
- /// <summary>
- /// Returns a token that represents the name of the property
- /// </summary>
- /// <param name = "name">The name for which to return a token</param>
- /// <returns>A 2 byte token representing the name</returns>
- private static ushort GetPropertyDefinitionId(string name)
- {
- lock (_propertyIds)
- {
- var ret = _propertyIds.IndexOf(name);
- if (ret >= 0)
- return (ushort)ret;
- _propertyIds.Add(name);
- return (ushort)(_propertyIds.Count - 1);
- }
- }
- /// <summary>
- /// Deserializes from a stream, potentially into an existing instance
- /// </summary>
- /// <param name="inputStream">Stream to deserialize from</param>
- /// <param name="instance">Instance to use</param>
- /// <returns></returns>
- public static object Deserialize(Stream inputStream, object instance = null)
- {
- var v = Verbose;
- CreateStacks();
- try
- {
- _ktStack.Push(_knownTypes);
- _piStack.Push(_propertyIds);
- _loStack.Push(_loadedObjects);
- var rw = new BinaryReader(inputStream);
- var version = rw.ReadString();
- var count = rw.ReadInt32();
- if (version == "SerV3")
- Verbose = rw.ReadBoolean();
- _propertyIds = new List<string>();
- _knownTypes = new List<Type>();
- _loadedObjects = new List<object>();
- for (var i = 0; i < count; i++)
- {
- var typeName = rw.ReadString();
- var tp = Type.GetType(typeName);
- if (tp == null)
- {
- var map = new TypeMappingEventArgs
- {
- TypeName = typeName
- };
- InvokeMapMissingType(map);
- tp = map.UseType;
- }
- if (!Verbose)
- if (tp == null)
- throw new ArgumentException(string.Format("Cannot reference type {0} in this context", typeName));
- _knownTypes.Add(tp);
- }
- count = rw.ReadInt32();
- for (var i = 0; i < count; i++)
- {
- _propertyIds.Add(rw.ReadString());
- }
- return DeserializeObject(rw, null, instance);
- }
- finally
- {
- _knownTypes = _ktStack.Pop();
- _propertyIds = _piStack.Pop();
- _loadedObjects = _loStack.Pop();
- Verbose = v;
- }
- }
- /// <summary>
- /// Convert a previously serialized object from a byte array
- /// back into a .NET object
- /// </summary>
- /// <param name = "bytes">The data stream for the object</param>
- /// <returns>The rehydrated object represented by the data supplied</returns>
- public static object Deserialize(byte[] bytes)
- {
- using (MemoryStream inputStream = new MemoryStream(bytes))
- {
- return Deserialize(inputStream);
- }
- }
- /// <summary>
- /// Convert a previously serialized object from a byte array
- /// back into a .NET object
- /// </summary>
- /// <param name = "bytes">The data stream for the object</param>
- /// <returns>The rehydrated object represented by the data supplied</returns>
- public static void DeserializeInto(byte[] bytes, object instance)
- {
- using (MemoryStream inputStream = new MemoryStream(bytes))
- {
- Deserialize(inputStream, instance);
- }
- }
- /// <summary>
- /// Creates a set of stacks on the current thread
- /// </summary>
- private static void CreateStacks()
- {
- if (_piStack == null)
- _piStack = new Stack<List<string>>();
- if (_ktStack == null)
- _ktStack = new Stack<List<Type>>();
- if (_loStack == null)
- _loStack = new Stack<List<object>>();
- if (_soStack == null)
- _soStack = new Stack<Dictionary<object, int>>();
- }
- /// <summary>
- /// Deserializes an object or primitive from the stream
- /// </summary>
- /// <param name = "reader">The reader of the binary file</param>
- /// <param name = "itemType">The expected type of the item being read (supports compact format)</param>
- /// <returns>The value read from the file</returns>
- /// <remarks>
- /// The function is supplied with the type of the property that the object was stored in (if known) this enables
- /// a compact format where types only have to be specified if they differ from the expected one
- /// </remarks>
- private static object DeserializeObject(BinaryReader reader, Type itemType = null, object instance = null)
- {
- var tpId = (ushort)reader.ReadUInt16();
- if (tpId == 0xFFFE)
- return null;
- //Lookup the value type if necessary
- if (tpId != 0xffff || itemType == null)
- itemType = _knownTypes[tpId];
- object obj = null;
- if (itemType != null)
- {
- //Check for custom serialization
- if (Serializers.ContainsKey(itemType))
- {
- //Read the serializer and its data
- var serializer = Serializers[itemType];
- object[] data = DeserializeObject(reader, typeof(object[])) as object[];
- return serializer.Deserialize(data);
- }
- //Check if this is a simple value and read it if so
- if (IsSimpleType(itemType))
- {
- if (itemType.IsEnum)
- {
- return Enum.Parse(itemType, ReadValue(reader, typeof(int)).ToString(), true);
- }
- return ReadValue(reader, itemType);
- }
- }
- //See if we should lookup this object or create a new one
- var found = reader.ReadChar();
- if (found == 'S') //S is for Seen
- return _loadedObjects[reader.ReadInt32()];
- if (itemType != null)
- {
- //Otherwise create the object
- if (itemType.IsArray)
- {
- int baseCount = reader.ReadInt32();
- if (baseCount == -1)
- {
- return DeserializeMultiDimensionArray(itemType, reader, baseCount);
- }
- else
- {
- return DeserializeArray(itemType, reader, baseCount);
- }
- }
- obj = instance ?? CreateObject(itemType);
- _loadedObjects.Add(obj);
- }
- //Check for collection types)
- if (obj is IDictionary)
- return DeserializeDictionary(obj as IDictionary, itemType, reader);
- if (obj is IList)
- return DeserializeList(obj as IList, itemType, reader);
- //Otherwise we are serializing an object
- return DeserializeObjectAndProperties(obj, itemType, reader);
- }
- /// <summary>
- /// Deserializes an array of values
- /// </summary>
- /// <param name = "itemType">The type of the array</param>
- /// <param name = "reader">The reader of the stream</param>
- /// <returns>The deserialized array</returns>
- /// <remarks>
- /// This routine optimizes for arrays of primitives and bytes
- /// </remarks>
- private static object DeserializeArray(Type itemType, BinaryReader reader, int count)
- {
- // If the count is -1 at this point, then it is being called from the
- // deserialization of a multi-dimensional array - so we need
- // to read the size of the array
- if (count == -1)
- {
- count = reader.ReadInt32();
- }
- //Get the expected element type
- var elementType = itemType.GetElementType();
- //Optimize for byte arrays
- if (elementType == typeof(byte))
- {
- var ret = reader.ReadBytes(count);
- _loadedObjects.Add(ret);
- return ret;
- }
- //Create an array of the correct type
- var array = Array.CreateInstance(elementType, count);
- _loadedObjects.Add(array);
- //Check whether the array contains primitives, if it does we don't
- //need to store the type of each member
- if (IsSimpleType(elementType))
- for (var l = 0; l < count; l++)
- {
- array.SetValue(ReadValue(reader, elementType), l);
- }
- else
- for (var l = 0; l < count; l++)
- {
- array.SetValue(DeserializeObject(reader, elementType), l);
- }
- return array;
- }
- /// <summary>
- /// Deserializes a multi-dimensional array of values
- /// </summary>
- /// <param name = "itemType">The type of the array</param>
- /// <param name = "reader">The reader of the stream</param>
- /// <param name="count">The base size of the multi-dimensional array</param>
- /// <returns>The deserialized array</returns>
- /// <remarks>
- /// This routine deserializes values serialized on a 'row by row' basis, and
- /// calls into DeserializeArray to do this
- /// </remarks>
- private static object DeserializeMultiDimensionArray(Type itemType, BinaryReader reader, int count)
- {
- //Read the number of dimensions the array has
- var dimensions = reader.ReadInt32();
- var totalLength = reader.ReadInt32();
- int rowLength = 0;
- // Establish the length of each array element
- // and get the total 'row size'
- int[] lengths = new int[dimensions];
- int[] indices = new int[dimensions];
- for (int item = 0; item < dimensions; item++)
- {
- lengths[item] = reader.ReadInt32();
- rowLength += lengths[item];
- indices[item] = 0;
- }
- int cols = lengths[lengths.Length - 1];
- //int cols = dimensions == 1 ? 1 : lengths[lengths.Length - 1];
- //Get the expected element type
- var elementType = itemType.GetElementType();
- Array sourceArrays = Array.CreateInstance(elementType, lengths);
- DeserializeArrayPart(sourceArrays, 0, indices, itemType, reader);
- return sourceArrays;
- }
- private static void DeserializeArrayPart(Array sourceArrays, int i, int[] indices, Type itemType, BinaryReader binaryReader)
- {
- int length = sourceArrays.GetLength(i);
- for (var l = 0; l < length; l++)
- {
- indices[i] = l;
- if (i != sourceArrays.Rank - 2)
- DeserializeArrayPart(sourceArrays, i + 1, indices, itemType, binaryReader);
- else
- {
- Array sourceArray = (Array)DeserializeArray(itemType, binaryReader, -1);
- int cols = sourceArrays.GetLength(i + 1);
- for (int arrayStartIndex = 0; arrayStartIndex < cols; arrayStartIndex++)
- {
- indices[i + 1] = arrayStartIndex;
- sourceArrays.SetValue(sourceArray.GetValue(arrayStartIndex), indices);
- }
- }
- }
- }
- /// <summary>
- /// Deserializes a dictionary from storage, handles generic types with storage optimization
- /// </summary>
- /// <param name = "o">The newly created dictionary</param>
- /// <param name = "itemType">The type of the dictionary</param>
- /// <param name = "reader">The binary reader for the current bytes</param>
- /// <returns>The dictionary object updated with the values from storage</returns>
- private static object DeserializeDictionary(IDictionary o, Type itemType, BinaryReader reader)
- {
- Type keyType = null;
- Type valueType = null;
- if (itemType.IsGenericType)
- {
- var types = itemType.GetGenericArguments();
- keyType = types[0];
- valueType = types[1];
- }
- var count = reader.ReadInt32();
- var list = new List<object>();
- for (var i = 0; i < count; i++)
- {
- list.Add(DeserializeObject(reader, keyType));
- }
- for (var i = 0; i < count; i++)
- {
- o[list[i]] = DeserializeObject(reader, valueType);
- }
- return o;
- }
- /// <summary>
- /// Deserialize a list from the data stream
- /// </summary>
- /// <param name = "o">The newly created list</param>
- /// <param name = "itemType">The type of the list</param>
- /// <param name = "reader">The reader for the current bytes</param>
- /// <returns>The list updated with values from the stream</returns>
- private static object DeserializeList(IList o, Type itemType, BinaryReader reader)
- {
- Type valueType = null;
- if (itemType.IsGenericType)
- {
- var types = itemType.GetGenericArguments();
- valueType = types[0];
- }
- var count = reader.ReadInt32();
- var list = new List<object>();
- for (var i = 0; i < count; i++)
- {
- o.Add(DeserializeObject(reader, valueType));
- }
- return o;
- }
- /// <summary>
- /// Deserializes a class based object that is not a collection, looks for both public properties and fields
- /// </summary>
- /// <param name = "o">The object being deserialized</param>
- /// <param name = "itemType">The type of the object</param>
- /// <param name = "reader">The reader for the current stream of bytes</param>
- /// <returns>The object updated with values from the stream</returns>
- private static object DeserializeObjectAndProperties(object o, Type itemType, BinaryReader reader)
- {
- DeserializeProperties(reader, itemType, o);
- DeserializeFields(reader, itemType, o);
- return o;
- }
- /// <summary>
- /// Deserializes the properties of an object from the stream
- /// </summary>
- /// <param name = "reader">The reader of the bytes in the stream</param>
- /// <param name = "itemType">The type of the object</param>
- /// <param name = "o">The object to deserialize</param>
- private static void DeserializeProperties(BinaryReader reader, Type itemType, object o)
- {
- //Get the number of properties
- var propCount = reader.ReadByte();
- int length = 0;
- if (Verbose)
- length = reader.ReadInt32();
- if (o == null)
- {
- reader.BaseStream.Seek(length, SeekOrigin.Current);
- return;
- }
- for (var i = 0; i < propCount; i++)
- {
- //Get a property name identifier
- var propId = reader.ReadUInt16();
- //Lookup the name
- var propName = _propertyIds[propId];
- //Use the name to find the type
- var propType = itemType.GetProperty(propName);
- //Deserialize the value
- var value = DeserializeObject(reader, propType != null ? propType.PropertyType : null);
- if (propType != null && value != null)
- {
- try
- {
- propType.SetValue(o, value, null);
- }
- catch (Exception)
- {
- //Suppress cases where the old value is no longer compatible with the new property type
- }
- }
- }
- }
- /// <summary>
- /// Deserializes the fields of an object from the stream
- /// </summary>
- /// <param name = "reader">The reader of the bytes in the stream</param>
- /// <param name = "itemType">The type of the object</param>
- /// <param name = "o">The object to deserialize</param>
- private static void DeserializeFields(BinaryReader reader, Type itemType, object o)
- {
- var fieldCount = reader.ReadByte();
- int length = 0;
- if (Verbose)
- length = reader.ReadInt32();
- if (o == null)
- {
- reader.BaseStream.Seek(length, SeekOrigin.Current);
- return;
- }
- for (var i = 0; i < fieldCount; i++)
- {
- var fieldId = reader.ReadUInt16();
- var fieldName = _propertyIds[fieldId];
- var fieldType = itemType.GetField(fieldName);
- var value = DeserializeObject(reader, fieldType != null ? fieldType.FieldType : null);
- if (fieldType != null && value != null)
- {
- try
- {
- fieldType.SetValue(o, value);
- }
- catch (Exception)
- {
- //Suppress cases where the old value is no longer compatible with the new property type
- }
- }
- }
- }
- public static void Serialize(object item, Stream outputStream)
- {
- CreateStacks();
- try
- {
- _ktStack.Push(_knownTypes);
- _piStack.Push(_propertyIds);
- _soStack.Push(_seenObjects);
- _propertyIds = new List<string>();
- _knownTypes = new List<Type>();
- _seenObjects = new Dictionary<object, int>();
- var strm = new MemoryStream();
- var wr = new BinaryWriter(strm);
- SerializeObject(item, wr);
- var outputWr = new BinaryWriter(outputStream);
- outputWr.Write("SerV3");
- outputWr.Write(_knownTypes.Count);
- //New, store the verbose property
- outputWr.Write(Verbose);
- foreach (var kt in _knownTypes)
- {
- outputWr.Write(kt.AssemblyQualifiedName);
- }
- outputWr.Write(_propertyIds.Count);
- foreach (var pi in _propertyIds)
- {
- outputWr.Write(pi);
- }
- strm.WriteTo(outputStream);
- }
- finally
- {
- _knownTypes = _ktStack.Pop();
- _propertyIds = _piStack.Pop();
- _seenObjects = _soStack.Pop();
- }
- }
- /// <summary>
- /// Serialize an object into an array of bytes
- /// </summary>
- /// <param name = "item">The object to serialize</param>
- /// <returns>A byte array representation of the item</returns>
- public static byte[] Serialize(object item)
- {
- using (MemoryStream outputStream = new MemoryStream())
- {
- Serialize(item, outputStream);
- //Reset the verbose mode
- return outputStream.ToArray();
- }
- }
- /// <summary>
- /// Serialize an object into an array of bytes
- /// </summary>
- /// <param name = "item">The object to serialize</param>
- /// <param name="makeVerbose">Whether the object should be serialized for forwards compatibility</param>
- /// <returns>A byte array representation of the item</returns>
- public static byte[] Serialize(object item, bool makeVerbose)
- {
- using (MemoryStream outputStream = new MemoryStream())
- {
- var v = Verbose;
- Verbose = makeVerbose;
- Serialize(item, outputStream);
- Verbose = v;
- //Reset the verbose mode
- return outputStream.ToArray();
- }
- }
- private static void SerializeObject(object item, BinaryWriter writer, Type propertyType = null)
- {
- if (item == null)
- {
- writer.Write((ushort)0xFFFE);
- return;
- }
- var itemType = item.GetType();
- Debug.Assert(itemType != null);
- //If this isn't a simple type, then this might be a subclass so we need to
- //store the type
- if (propertyType != itemType || Verbose)
- {
- //Write the type identifier
- var tpId = GetTypeId(itemType);
- writer.Write(tpId);
- }
- else
- //Write a dummy identifier
- writer.Write((ushort)0xFFFF);
- //Check for custom serialization
- if (Serializers.ContainsKey(itemType))
- {
- //If we have a custom serializer then use it!
- var serializer = Serializers[itemType];
- var data = serializer.Serialize(item);
- SerializeObject(data, writer, typeof(object[]));
- return;
- }
- //Check for simple types again
- if (IsSimpleType(itemType))
- {
- if (itemType.IsEnum)
- WriteValue(writer, (int)item);
- else
- WriteValue(writer, item);
- return;
- }
- //Check whether this object has been seen
- if (_seenObjects.ContainsKey(item))
- {
- writer.Write('S');
- writer.Write(_seenObjects[item]);
- return;
- }
- //We are going to serialize an object
- writer.Write('O');
- _seenObjects[item] = _seenObjects.Count;
- //Check for collection types)
- if (item is Array)
- {
- if (((Array)item).Rank == 1)
- {
- SerializeArray(item as Array, itemType, writer);
- }
- else
- {
- SerializeMultiDimensionArray(item as Array, itemType, writer);
- }
- return;
- }
- if (item is IDictionary)
- {
- SerializeDictionary(item as IDictionary, itemType, writer);
- return;
- }
- if (item is IList)
- {
- SerializeList(item as IList, itemType, writer);
- return;
- }
- //Otherwise we are serializing an object
- SerializeObjectAndProperties(item, itemType, writer);
- }
- private static void SerializeList(IList item, Type tp, BinaryWriter writer)
- {
- Type valueType = null;
- //Try to optimize the storage of types based on the type of list
- if (tp.IsGenericType)
- {
- var types = tp.GetGenericArguments();
- valueType = types[0];
- }
- writer.Write(item.Count);
- foreach (var val in item)
- {
- SerializeObject(val, writer, valueType);
- }
- }
- private static void SerializeDictionary(IDictionary item, Type tp, BinaryWriter writer)
- {
- Type keyType = null;
- Type valueType = null;
- //Try to optimise storage based on the type of dictionary
- if (tp.IsGenericType)
- {
- var types = tp.GetGenericArguments();
- keyType = types[0];
- valueType = types[1];
- }
- //Write out the size
- writer.Write(item.Count);
- //Serialize the pairs
- foreach (var key in item.Keys)
- {
- SerializeObject(key, writer, keyType);
- }
- foreach (var val in item.Values)
- {
- SerializeObject(val, writer, valueType);
- }
- }
- private static void SerializeArray(Array item, Type tp, BinaryWriter writer)
- {
- var length = item.Length;
- writer.Write(length);
- var propertyType = tp.GetElementType();
- //Special optimization for arrays of byte
- if (propertyType == typeof(byte))
- writer.Write((byte[])item, 0, length);
- //Special optimization for arrays of simple types
- //which don't need to have the entry type stored
- //for each item
- else if (IsSimpleType(propertyType))
- for (var l = 0; l < length; l++)
- {
- WriteValue(writer, item.GetValue(l));
- }
- else
- for (var l = 0; l < length; l++)
- {
- SerializeObject(item.GetValue(l), writer, propertyType);
- }
- }
- private static void SerializeMultiDimensionArray(Array item, Type tp, BinaryWriter writer)
- {
- // Multi-dimension serializer data is:
- // Int32: Ranks
- // Int32 (x number of ranks): length of array dimension
- int dimensions = item.Rank;
- var length = item.GetLength(0);
- // Determine the number of cols being populated
- var cols = item.GetLength(item.Rank - 1);
- // Explicitly write this value, to denote that this is a multi-dimensional array
- // so it doesn't break the deserializer when reading values for existing arrays
- writer.Write((int)-1);
- writer.Write(dimensions);
- writer.Write(item.Length);
- var propertyType = tp.GetElementType();
- var indicies = new int[dimensions];
- // Write out the length of each array, if we are dealing with the first array
- for (int arrayStartIndex = 0; arrayStartIndex < dimensions; arrayStartIndex++)
- {
- indicies[arrayStartIndex] = 0;
- writer.Write(item.GetLength(arrayStartIndex));
- }
- SerializeArrayPart(item, 0, indicies, writer);
- }
- private static void SerializeArrayPart(Array item, int i, int[] indices, BinaryWriter writer)
- {
- var length = item.GetLength(i);
- for (var l = 0; l < length; l++)
- {
- indices[i] = l;
- if (i != item.Rank - 2)
- SerializeArrayPart(item, i + 1, indices, writer);
- else
- {
- Type arrayType = item.GetType().GetElementType();
- var cols = item.GetLength(i + 1);
- var baseArray = Array.CreateInstance(arrayType, cols);
- // Convert the whole multi-dimensional array to be 'row' based
- // and serialize using the existing code
- for (int arrayStartIndex = 0; arrayStartIndex < cols; arrayStartIndex++)
- {
- indices[i + 1] = arrayStartIndex;
- baseArray.SetValue(item.GetValue(indices), arrayStartIndex);
- }
- SerializeArray(baseArray, baseArray.GetType(), writer);
- }
- }
- }
- /// <summary>
- /// Return whether the type specified is a simple type that can be serialized fast
- /// </summary>
- /// <param name = "tp">The type to check</param>
- /// <returns>True if the type is a simple one and can be serialized directly</returns>
- private static bool IsSimpleType(Type tp)
- {
- return tp.IsPrimitive || tp == typeof(DateTime) || tp == typeof(TimeSpan) || tp == typeof(string) || tp.IsEnum || tp == typeof(Guid) || tp == typeof(decimal);
- }
- private static void SerializeObjectAndProperties(object item, Type itemType, BinaryWriter writer)
- {
- lock (Vanilla)
- {
- if (Vanilla.ContainsKey(itemType) == false)
- {
- Vanilla[itemType] = CreateObject(itemType);
- }
- }
- WriteProperties(itemType, item, writer);
- WriteFields(itemType, item, writer);
- }
- private static object CreateObject(Type itemType)
- {
- try
- {
- return Activator.CreateInstance(itemType);
- }
- catch (Exception)
- {
- return itemType.GetConstructor(new Type[] { }).Invoke(new object[] { });
- }
- }
- private static void WriteProperties(Type itemType, object item, BinaryWriter writer)
- {
- var propertyStream = new MemoryStream();
- var pw = new BinaryWriter(propertyStream);
- byte propCount = 0;
- //Get the properties of the object
- var properties = GetPropertyInfo(itemType);
- foreach (var property in properties)
- {
- if (IsChecksum && IsLoud)
- {
- Debug.WriteLine(string.Format(" ----> {0} on {1}", property.Name, item.ToString()));
- }
- var value = property.GetValue(item, null);
- //Don't store null values
- if (value == null)
- continue;
- //Don't store empty collections
- if (value is ICollection)
- if ((value as ICollection).Count == 0)
- continue;
- //Don't store empty arrays
- if (value is Array)
- if ((value as Array).Length == 0)
- continue;
- //Check whether the value differs from the default
- lock (Vanilla)
- {
- if (value.Equals(property.GetValue(Vanilla[itemType], null)))
- continue;
- }
- //If we get here then we need to store the property
- propCount++;
- pw.Write(GetPropertyDefinitionId(property.Name));
- SerializeObject(value, pw, property.PropertyType);
- }
- writer.Write(propCount);
- if (Verbose)
- writer.Write((int)propertyStream.Length);
- propertyStream.WriteTo(writer.BaseStream);
- }
- private static void WriteFields(Type itemType, object item, BinaryWriter writer)
- {
- var fieldStream = new MemoryStream();
- var fw = new BinaryWriter(fieldStream);
- byte fieldCount = 0;
- //Get the public fields of the object
- var fields = GetFieldInfo(itemType);
- foreach (var field in fields)
- {
- var value = field.GetValue(item);
- //Don't store null values
- if (value == null)
- continue;
- //Don't store empty collections
- if (value is ICollection)
- if ((value as ICollection).Count == 0)
- continue;
- //Don't store empty arrays
- if (value is Array)
- if ((value as Array).Length == 0)
- continue;
- //Check whether the value differs from the default
- lock (Vanilla)
- {
- if (value.Equals(field.GetValue(Vanilla[itemType])))
- continue;
- }
- //if we get here then we need to store the field
- fieldCount++;
- fw.Write(GetPropertyDefinitionId(field.Name));
- SerializeObject(value, fw, field.FieldType);
- }
- writer.Write(fieldCount);
- if (Verbose)
- writer.Write((int)fieldStream.Length);
- fieldStream.WriteTo(writer.BaseStream);
- }
- /// <summary>
- /// Write a basic untyped value
- /// </summary>
- /// <param name = "writer">The writer to commit byte to</param>
- /// <param name = "value">The value to write</param>
- private static void WriteValue(BinaryWriter writer, object value)
- {
- if (value is string)
- writer.Write((string)value);
- else if (value == null)
- writer.Write("~~NULL~~");
- else if (value is decimal)
- {
- int[] array = Decimal.GetBits((Decimal)value);
- SerializeObject(array, writer, typeof(int[]));
- }
- else if (value is float)
- writer.Write((float)value);
- else if (value is bool)
- writer.Write((bool)value
- ? 'Y'
- : 'N');
- else if (value is Guid)
- writer.Write(value.ToString());
- else if (value is DateTime)
- writer.Write(((DateTime)value).Ticks);
- else if (value is TimeSpan)
- writer.Write(((TimeSpan)value).Ticks);
- else if (value is char)
- writer.Write((char)value);
- else if (value is ushort)
- writer.Write((ushort)value);
- else if (value is double)
- writer.Write((double)value);
- else if (value is ulong)
- writer.Write((ulong)value);
- else if (value is int)
- writer.Write((int)value);
- else if (value is uint)
- writer.Write((uint)value);
- else if (value is byte)
- writer.Write((byte)value);
- else if (value is long)
- writer.Write((long)value);
- else if (value is short)
- writer.Write((short)value);
- else if (value is sbyte)
- writer.Write((sbyte)value);
- else
- writer.Write((int)value);
- }
- /// <summary>
- /// Read a basic value from the stream
- /// </summary>
- /// <param name = "reader">The reader with the stream</param>
- /// <param name = "tp">The type to read</param>
- /// <returns>The hydrated value</returns>
- private static object ReadValue(BinaryReader reader, Type tp)
- {
- if (tp == typeof(string))
- {
- var retString = reader.ReadString();
- return retString == "~~NULL~~"
- ? null
- : retString;
- }
- if (tp == typeof(bool))
- return reader.ReadChar() == 'Y';
- if (tp == typeof(decimal))
- {
- var array = DeserializeObject(reader, typeof(int[])) as int[];
- return new Decimal(array);
- }
- if (tp == typeof(DateTime))
- return new DateTime(reader.ReadInt64());
- if (tp == typeof(TimeSpan))
- return new TimeSpan(reader.ReadInt64());
- if (tp == typeof(float))
- return reader.ReadSingle();
- if (tp == typeof(char))
- return reader.ReadChar();
- if (tp == typeof(ushort))
- return reader.ReadUInt16();
- if (tp == typeof(double))
- return reader.ReadDouble();
- if (tp == typeof(ulong))
- return reader.ReadUInt64();
- if (tp == typeof(int))
- return reader.ReadInt32();
- if (tp == typeof(uint))
- return reader.ReadUInt32();
- if (tp == typeof(byte))
- return reader.ReadByte();
- if (tp == typeof(long))
- return reader.ReadInt64();
- if (tp == typeof(short))
- return reader.ReadInt16…
Large files files are truncated, but you can click here to view the full file