#define THROW_IF_NOT_OPTIMIZABLE using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.Serialization.Formatters; using System.Runtime.Serialization.Formatters.Binary; using System.Text; // ReSharper disable PossibleInvalidCastException // ReSharper disable PossibleNullReferenceException // ReSharper disable AssignNullToNotNullAttribute // ReSharper disable ConditionIsAlwaysTrueOrFalse // ReSharper disable HeuristicUnreachableCode namespace DelftTools.Utils.Serialization { /// /// Class which defines the writer for serialized data using the fast serialization optimization. /// A SerializationWriter instance is used to store values and objects in a byte array. ///

/// Once an instance is created, use the various methods to store the required data. /// ToArray() will return a byte[] containing all of the data required for deserialization. /// This can be stored in the SerializationInfo parameter in an ISerializable.GetObjectData() method. /// /// As an alternative to ToArray(), if you want to apply some post-processing to the serialized bytes, /// such as compression, call UpdateHeader() first to ensure that the string and object token table /// sizes are updated in the header, and then cast BaseStream to MemoryStream. You can then access the /// MemoryStream's internal buffer as follows: /// /// /// writer.UpdateHeader(); /// MemoryStream stream = (MemoryStream) writer.BaseStream; /// serializedData = MyCompressor.Compress(stream.GetBuffer(), (int) stream.Length); /// ///
public sealed class SerializationWriter : BinaryWriter { #region Statics and constants /// /// Default capacity for the underlying MemoryStream /// public static int DefaultCapacity = 1024; /// /// The Default setting for the OptimizeForSize property. /// public static bool DefaultOptimizeForSize = true; /// /// The Default setting for the PreserveDecimalScale property. /// public static bool DefaultPreserveDecimalScale; /// /// Holds a list of optional IFastSerializationTypeSurrogate instances which SerializationWriter and SerializationReader will use to serialize objects /// not directly supported. It is important to use the same list on both client and server ends to ensure /// that the same surrogated-types are supported. /// private static readonly List typeSurrogates = new List(); /// /// Section masks used for packing DateTime values /// internal static readonly BitVector32.Section DateYearMask = BitVector32.CreateSection(9999); //14 bits internal static readonly BitVector32.Section DateMonthMask = BitVector32.CreateSection(12, DateYearMask); // 4 bits internal static readonly BitVector32.Section DateDayMask = BitVector32.CreateSection(31, DateMonthMask); // 5 bits internal static readonly BitVector32.Section DateHasTimeOrKindMask = BitVector32.CreateSection(1, DateDayMask); // 1 bit total= 3 bytes /// /// Section masks used for packing TimeSpan values /// internal static readonly BitVector32.Section IsNegativeSection = BitVector32.CreateSection(1); //1 bit internal static readonly BitVector32.Section HasDaysSection = BitVector32.CreateSection(1, IsNegativeSection); //1 bit internal static readonly BitVector32.Section HasTimeSection = BitVector32.CreateSection(1, HasDaysSection); //1 bit internal static readonly BitVector32.Section HasSecondsSection = BitVector32.CreateSection(1, HasTimeSection); //1 bit internal static readonly BitVector32.Section HasMillisecondsSection = BitVector32.CreateSection(1, HasSecondsSection); //1 bit internal static readonly BitVector32.Section HoursSection = BitVector32.CreateSection(23, HasMillisecondsSection); // 5 bits internal static readonly BitVector32.Section MinutesSection = BitVector32.CreateSection(59, HoursSection); // 6 bits total = 2 bytes internal static readonly BitVector32.Section SecondsSection = BitVector32.CreateSection(59, MinutesSection); // 6 bits total = 3 bytes internal static readonly BitVector32.Section MillisecondsSection = BitVector32.CreateSection(1024, SecondsSection); // 10 bits - total 31 bits = 4 bytes /// /// Holds the highest Int16 that can be optimized into less than the normal 2 bytes /// public const short HighestOptimizable16BitValue = 127; // 0x7F /// /// Holds the highest Int32 that can be optimized into less than the normal 4 bytes /// public const int HighestOptimizable32BitValue = 2097151; // 0x001FFFFF /// /// Holds the highest Int64 that can be optimized into less than the normal 8 bytes /// public const long HighestOptimizable64BitValue = 562949953421311; // 0x0001FFFFFFFFFFFF // The short at which optimization fails because it takes more than 2 bytes internal const short OptimizationFailure16BitValue = 16384; // The int at which optimization fails because it takes more than 4 bytes internal const int OptimizationFailure32BitValue = 268435456; // 0x10000000 // The long at which optimization fails because it takes more than 8 bytes internal const long OptimizationFailure64BitValue = 72057594037927936; // 0x0100000000000000 // Marker to denote that all elements in a typed array are optimizable private static readonly BitArray FullyOptimizableTypedArray = new BitArray(0); #endregion private readonly UniqueStringList stringLookup; private readonly Hashtable objectTokenLookup; private readonly bool allowUpdateHeader; private readonly int startPosition; #region Type Usage related code (Debug mode only) #if DEBUG // type usage member in which counters are stored of all the known types emitted into the output stream. For debugging purposes. public int[] TypeUsage { get { return typeUsage; } } readonly int[] typeUsage = new int[256]; // stores the number of bytes used for tokenized strings and objects public int TableBytes { get { return tableBytes; } } int tableBytes; #endif /// /// Dumps the type usage. /// [Conditional("DEBUG")] public void DumpTypeUsage() { var sb = new StringBuilder("Type Usage Dump\r\n---------------\r\n"); for (var i = 0; i < 256; i++) { #if DEBUG if (typeUsage[i] != 0) { sb.AppendFormat("{0, 8:n0}: {1}\r\n", typeUsage[i], (SerializedType) i); } #endif } Console.WriteLine(sb); } #endregion /// /// Creates a FastSerializer with the Default Capacity (1kb) /// public SerializationWriter() : this(new MemoryStream(DefaultCapacity)) {} /// /// Creates a FastSerializer with the specified capacity /// /// public SerializationWriter(int capacity) : this(new MemoryStream(capacity)) {} /// /// Creates a FastSerializer around the specified stream /// Will allow updating of header info if the stream is seekable /// /// public SerializationWriter(Stream stream) : this(stream, true) {} /// /// Creates a FastSerializer around the specified stream /// Notes: /// If the stream is not seekable then the allowUpdateHeader parameter is ignored /// /// The stream in which to store data /// true if token table presize /// information can be stored; false otherwise public SerializationWriter(Stream stream, bool allowUpdateHeader) : base(stream) { // Store the start position of the stream if seekable startPosition = stream.CanSeek ? (int) stream.Position : 0; // Stream must also be seekable for this field to be set to true this.allowUpdateHeader = allowUpdateHeader && stream.CanSeek; // Always write an Int32 placeholder; // it will store either the stream length or remain as 0 if allowUpdateHeader is false Write(0); if (this.allowUpdateHeader) { // Write additional placeholders for tokenized string count and tokenized object count Write(0); Write(0); } objectTokenLookup = new Hashtable(); stringLookup = new UniqueStringList(); OptimizeForSize = DefaultOptimizeForSize; PreserveDecimalScale = DefaultPreserveDecimalScale; } /// /// Writes an ArrayList into the stream using the fewest number of bytes possible. /// Stored Size: 1 byte upwards depending on data content /// Notes: /// A null Arraylist takes 1 byte. /// An empty ArrayList takes 2 bytes. /// The contents are stored using WriteOptimized(ArrayList) which should be used /// if the ArrayList is guaranteed never to be null. /// /// The ArrayList to store. public void Write(ArrayList value) { if (value == null) { WriteTypeCode(SerializedType.NullType); } else { WriteTypeCode(SerializedType.ArrayListType); WriteOptimized(value); } } /// /// Writes a BitArray value into the stream using the fewest number of bytes possible. /// Stored Size: 1 byte upwards depending on data content /// Notes: /// A null BitArray takes 1 byte. /// An empty BitArray takes 2 bytes. /// /// The BitArray value to store. public void Write(BitArray value) { if (value == null) { WriteTypeCode(SerializedType.NullType); } else { WriteTypeCode(SerializedType.BitArrayType); WriteOptimized(value); } } /// /// Writes a BitVector32 into the stream. /// Stored Size: 4 bytes. /// /// The BitVector32 to store. public void Write(BitVector32 value) { Write(value.Data); } /// /// Writes a DateTime value into the stream. /// Stored Size: 8 bytes /// /// The DateTime value to store. public void Write(DateTime value) { Write(value.ToBinary()); } /// /// Writes a Guid into the stream. /// Stored Size: 16 bytes. /// /// public void Write(Guid value) { base.Write(value.ToByteArray()); } /// /// Allows any object implementing IOwnedDataSerializable to serialize itself /// into this SerializationWriter. /// A context may also be used to give the object an indication of what data /// to store. As an example, using a BitVector32 gives a list of flags and /// the object can conditionally store data depending on those flags. /// /// The IOwnedDataSerializable object to ask for owned data /// An arbtritrary object but BitVector32 recommended public void Write(IOwnedDataSerializable target, object context) { target.SerializeOwnedData(this, context); } /// /// Stores an object into the stream using the fewest number of bytes possible. /// Stored Size: 1 byte upwards depending on type and/or content. /// /// 1 byte: null, DBNull.Value, Boolean /// /// 1 to 2 bytes: Int16, UInt16, Byte, SByte, Char, /// /// 1 to 4 bytes: Int32, UInt32, Single, BitVector32 /// /// 1 to 8 bytes: DateTime, TimeSpan, Double, Int64, UInt64 /// /// 1 or 16 bytes: Guid /// /// 1 plus content: string, object[], byte[], char[], BitArray, Type, ArrayList /// /// Any other object be stored using a .Net Binary formatter but this should /// only be allowed as a last resort: /// Since this is effectively a different serialization session, there is a /// possibility of the same shared object being serialized twice or, if the /// object has a reference directly or indirectly back to the parent object, /// there is a risk of looping which will throw an exception. /// /// The type of object is checked with the most common types being checked first. /// Each 'section' can be reordered to provide optimum speed but the check for /// null should always be first and the default serialization always last. /// /// Once the type is identified, a SerializedType byte is stored in the stream /// followed by the data for the object (certain types/values may not require /// storage of data as the SerializedType may imply the value). /// /// For certain objects, if the value is within a certain range then optimized /// storage may be used. If the value doesn't meet the required optimization /// criteria then the value is stored directly. /// The checks for optimization may be disabled by setting the OptimizeForSize /// property to false in which case the value is stored directly. This could /// result in a slightly larger stream but there will be a speed increate to /// compensate. /// /// The object to store. public void WriteObject(object value) { // The following routine uses a main if-else tree which is somewhat flattened. Every if/else branch simply // tests for the type of value. If a type match is found, the code uses a normal if/else tree. if (value == null) { WriteTypeCode(SerializedType.NullType); } else if (value is string) { WriteOptimized((string) value); } else if (value is Int32) { var int32Value = (int) value; switch (int32Value) { case 0: WriteTypeCode(SerializedType.ZeroInt32Type); return; case -1: WriteTypeCode(SerializedType.MinusOneInt32Type); return; case 1: WriteTypeCode(SerializedType.OneInt32Type); return; default: if (OptimizeForSize) { if (int32Value > 0) { if (int32Value <= HighestOptimizable32BitValue) { WriteTypeCode(SerializedType.OptimizedInt32Type); Write7BitEncodedSigned32BitValue(int32Value); return; } } else { var positiveInt32Value = -(int32Value + 1); if (positiveInt32Value <= HighestOptimizable32BitValue) { WriteTypeCode(SerializedType.OptimizedInt32NegativeType); Write7BitEncodedSigned32BitValue(positiveInt32Value); return; } } } WriteTypeCode(SerializedType.Int32Type); Write(int32Value); return; } } else if (value == DBNull.Value) { WriteTypeCode(SerializedType.DBNullType); } else if (value is Boolean) { WriteTypeCode((bool) value ? SerializedType.BooleanTrueType : SerializedType.BooleanFalseType); } else if (value is Decimal) { var decimalValue = (Decimal) value; if (decimalValue == 0) { WriteTypeCode(SerializedType.ZeroDecimalType); } else if (decimalValue == 1) { WriteTypeCode(SerializedType.OneDecimalType); } else { WriteTypeCode(SerializedType.DecimalType); WriteOptimized(decimalValue); } } else if (value is DateTime) { var dateTimeValue = (DateTime) value; if (dateTimeValue == DateTime.MinValue) { WriteTypeCode(SerializedType.MinDateTimeType); } else if (dateTimeValue == DateTime.MaxValue) { WriteTypeCode(SerializedType.MaxDateTimeType); } else if (OptimizeForSize && ((dateTimeValue.Ticks%TimeSpan.TicksPerMillisecond) == 0)) { WriteTypeCode(SerializedType.OptimizedDateTimeType); WriteOptimized(dateTimeValue); } else { WriteTypeCode(SerializedType.DateTimeType); Write(dateTimeValue); } } else if (value is Double) { var doubleValue = (Double) value; if (doubleValue == 0) { WriteTypeCode(SerializedType.ZeroDoubleType); } else if (doubleValue == 1) { WriteTypeCode(SerializedType.OneDoubleType); } else { WriteTypeCode(SerializedType.DoubleType); Write(doubleValue); } } else if (value is Single) { var singleValue = (Single) value; if (singleValue == 0) { WriteTypeCode(SerializedType.ZeroSingleType); } else if (singleValue == 1) { WriteTypeCode(SerializedType.OneSingleType); } else { WriteTypeCode(SerializedType.SingleType); Write(singleValue); } } else if (value is Int16) { var int16Value = (Int16) value; switch (int16Value) { case 0: WriteTypeCode(SerializedType.ZeroInt16Type); return; case -1: WriteTypeCode(SerializedType.MinusOneInt16Type); return; case 1: WriteTypeCode(SerializedType.OneInt16Type); return; default: if (OptimizeForSize) { if (int16Value > 0) { if (int16Value <= HighestOptimizable16BitValue) { WriteTypeCode(SerializedType.OptimizedInt16Type); Write7BitEncodedSigned32BitValue(int16Value); return; } } else { var positiveInt16Value = (-(int16Value + 1)); if (positiveInt16Value <= HighestOptimizable16BitValue) { WriteTypeCode(SerializedType.OptimizedInt16NegativeType); Write7BitEncodedSigned32BitValue(positiveInt16Value); return; } } } WriteTypeCode(SerializedType.Int16Type); Write(int16Value); return; } } else if (value is Guid) { var guidValue = (Guid) value; if (guidValue == Guid.Empty) { WriteTypeCode(SerializedType.EmptyGuidType); } else { WriteTypeCode(SerializedType.GuidType); Write(guidValue); } return; } else if (value is Int64) { var int64Value = (Int64) value; switch (int64Value) { case 0: WriteTypeCode(SerializedType.ZeroInt64Type); return; case -1: WriteTypeCode(SerializedType.MinusOneInt64Type); return; case 1: WriteTypeCode(SerializedType.OneInt64Type); return; default: if (OptimizeForSize) { if (int64Value > 0) { if (int64Value <= HighestOptimizable64BitValue) { WriteTypeCode(SerializedType.OptimizedInt64Type); Write7BitEncodedSigned64BitValue(int64Value); return; } } else { var positiveInt64Value = -(int64Value + 1); if (positiveInt64Value <= HighestOptimizable64BitValue) { WriteTypeCode(SerializedType.OptimizedInt64NegativeType); Write7BitEncodedSigned64BitValue(positiveInt64Value); return; } } } WriteTypeCode(SerializedType.Int64Type); Write(int64Value); return; } } else if (value is Byte) { var byteValue = (Byte) value; switch (byteValue) { case 0: WriteTypeCode(SerializedType.ZeroByteType); return; case 1: WriteTypeCode(SerializedType.OneByteType); return; default: WriteTypeCode(SerializedType.ByteType); Write(byteValue); return; } } else if (value is Char) { var charValue = (Char) value; switch (charValue) { case (Char) 0: WriteTypeCode(SerializedType.ZeroCharType); return; case (Char) 1: WriteTypeCode(SerializedType.OneCharType); return; default: WriteTypeCode(SerializedType.CharType); Write(charValue); return; } } else if (value is SByte) { var sbyteValue = (SByte) value; switch (sbyteValue) { case 0: WriteTypeCode(SerializedType.ZeroSByteType); return; case 1: WriteTypeCode(SerializedType.OneSByteType); return; default: WriteTypeCode(SerializedType.SByteType); Write(sbyteValue); return; } } else if (value is UInt32) { var uint32Value = (UInt32) value; switch (uint32Value) { case 0: WriteTypeCode(SerializedType.ZeroUInt32Type); return; case 1: WriteTypeCode(SerializedType.OneUInt32Type); return; default: if (OptimizeForSize && uint32Value <= HighestOptimizable32BitValue) { WriteTypeCode(SerializedType.OptimizedUInt32Type); Write7BitEncodedUnsigned32BitValue(uint32Value); } else { WriteTypeCode(SerializedType.UInt32Type); Write(uint32Value); } return; } } else if (value is UInt16) { var uint16Value = (UInt16) value; switch (uint16Value) { case 0: WriteTypeCode(SerializedType.ZeroUInt16Type); return; case 1: WriteTypeCode(SerializedType.OneUInt16Type); return; default: if (OptimizeForSize && uint16Value <= HighestOptimizable16BitValue) { WriteTypeCode(SerializedType.OptimizedUInt16Type); Write7BitEncodedUnsigned32BitValue(uint16Value); } else { WriteTypeCode(SerializedType.UInt16Type); Write(uint16Value); } return; } } else if (value is UInt64) { var uint64Value = (UInt64) value; switch (uint64Value) { case 0: WriteTypeCode(SerializedType.ZeroUInt64Type); return; case 1: WriteTypeCode(SerializedType.OneUInt64Type); return; default: if (OptimizeForSize && uint64Value <= HighestOptimizable64BitValue) { WriteTypeCode(SerializedType.OptimizedUInt64Type); WriteOptimized(uint64Value); } else { WriteTypeCode(SerializedType.UInt64Type); Write(uint64Value); } return; } } else if (value is TimeSpan) { var timeSpanValue = (TimeSpan) value; if (timeSpanValue == TimeSpan.Zero) { WriteTypeCode(SerializedType.ZeroTimeSpanType); } else if (OptimizeForSize && (timeSpanValue.Ticks%TimeSpan.TicksPerMillisecond) == 0) { WriteTypeCode(SerializedType.OptimizedTimeSpanType); WriteOptimized(timeSpanValue); } else { WriteTypeCode(SerializedType.TimeSpanType); Write(timeSpanValue); } return; } else if (value is Array) { WriteTypedArray((Array) value, true); } else if (value is Type) { WriteTypeCode(SerializedType.TypeType); WriteOptimized((value as Type)); } else if (value is BitArray) { WriteTypeCode(SerializedType.BitArrayType); WriteOptimized((BitArray) value); } else if (value is BitVector32) { WriteTypeCode(SerializedType.BitVector32Type); Write((BitVector32) value); } else if (IsTypeRecreatable(value.GetType())) { WriteTypeCode(SerializedType.OwnedDataSerializableAndRecreatableType); WriteOptimized(value.GetType()); Write((IOwnedDataSerializable) value, null); } else if (value is SingletonTypeWrapper) { WriteTypeCode(SerializedType.SingleInstanceType); WriteStringDirect((value as SingletonTypeWrapper).WrappedType.AssemblyQualifiedName); } else if (value is ArrayList) { WriteTypeCode(SerializedType.ArrayListType); WriteOptimized((value as ArrayList)); } else if (value is Enum) { var enumType = value.GetType(); var underlyingType = Enum.GetUnderlyingType(enumType); switch (Type.GetTypeCode(underlyingType)) { case TypeCode.Int32: case TypeCode.UInt32: var uint32Value = underlyingType == typeof(int) ? (uint) (int) value : (uint) value; if (uint32Value <= HighestOptimizable32BitValue) { WriteTypeCode(SerializedType.OptimizedEnumType); WriteOptimized(enumType); Write7BitEncodedUnsigned32BitValue(uint32Value); } else { WriteTypeCode(SerializedType.EnumType); WriteOptimized(enumType); Write(uint32Value); } return; case TypeCode.Int64: case TypeCode.UInt64: var uint64Value = underlyingType == typeof(long) ? (ulong) (long) value : (ulong) value; if (uint64Value <= HighestOptimizable64BitValue) { WriteTypeCode(SerializedType.OptimizedEnumType); WriteOptimized(enumType); Write7BitEncodedUnsigned64BitValue(uint64Value); } else { WriteTypeCode(SerializedType.EnumType); WriteOptimized(enumType); Write(uint64Value); } return; case TypeCode.Byte: WriteTypeCode(SerializedType.EnumType); WriteOptimized(enumType); Write((byte) value); return; case TypeCode.SByte: WriteTypeCode(SerializedType.EnumType); WriteOptimized(enumType); Write((sbyte) value); return; case TypeCode.Int16: WriteTypeCode(SerializedType.EnumType); WriteOptimized(enumType); Write((short) value); return; default: WriteTypeCode(SerializedType.EnumType); WriteOptimized(enumType); Write((ushort) value); return; } } else { var valueType = value.GetType(); var typeSurrogate = FindSurrogateForType(valueType); if (typeSurrogate != null) { WriteTypeCode(SerializedType.SurrogateHandledType); WriteOptimized(valueType); typeSurrogate.Serialize(this, value); } else { WriteTypeCode(SerializedType.OtherType); CreateBinaryFormatter().Serialize(BaseStream, value); } } } /// /// Calls WriteOptimized(string). /// This override to hide base BinaryWriter.Write(string). /// /// The string to store. public override void Write(string value) { WriteOptimized(value); } /// /// Writes a TimeSpan value into the stream. /// Stored Size: 8 bytes /// /// The TimeSpan value to store. public void Write(TimeSpan value) { Write(value.Ticks); } /// /// Stores a Type object into the stream. /// Stored Size: Depends on the length of the Type's name and whether the fullyQualified parameter is set. /// A null Type takes 1 byte. /// /// The Type to store. /// true to store the AssemblyQualifiedName or false to store the FullName. public void Write(Type value, bool fullyQualified) { if (value == null) { WriteTypeCode(SerializedType.NullType); } else { WriteTypeCode(SerializedType.TypeType); WriteOptimized(fullyQualified ? value.AssemblyQualifiedName : value.FullName); } } /// /// Writes an non-null ArrayList into the stream using the fewest number of bytes possible. /// Stored Size: 1 byte upwards depending on data content /// Notes: /// An empty ArrayList takes 1 byte. /// /// The ArrayList to store. Must not be null. public void WriteOptimized(ArrayList value) { CheckOptimizable(value != null, "Cannot optimize a null ArrayList"); WriteObjectArray(value.ToArray()); } /// /// Writes a BitArray into the stream using the fewest number of bytes possible. /// Stored Size: 1 byte upwards depending on data content /// Notes: /// An empty BitArray takes 1 byte. /// /// The BitArray value to store. Must not be null. public void WriteOptimized(BitArray value) { CheckOptimizable(value != null, "Cannot optimize a null BitArray"); Write7BitEncodedSigned32BitValue(value.Length); if (value.Length > 0) { var data = new byte[(value.Length + 7)/8]; value.CopyTo(data, 0); Write(data, 0, data.Length); } } /// /// Writes a BitVector32 into the stream using the fewest number of bytes possible. /// Stored Size: 1 to 4 bytes. (.Net is 4 bytes) /// 1 to 7 bits takes 1 byte /// 8 to 14 bits takes 2 bytes /// 15 to 21 bits takes 3 bytes /// 22 to 28 bits takes 4 bytes /// ------------------------------------------------------------------- /// 29 to 32 bits takes 5 bytes - use Write(BitVector32) method instead /// /// Try to order the BitVector32 masks so that the highest bits are least-likely /// to be set. /// /// The BitVector32 to store. Must not use more than 28 bits. public void WriteOptimized(BitVector32 value) { CheckOptimizable(value.Data < OptimizationFailure32BitValue && value.Data >= 0, "BitVector32 value is not optimizable"); Write7BitEncodedSigned32BitValue(value.Data); } /// /// Writes a DateTime value into the stream using the fewest number of bytes possible. /// Stored Size: 3 bytes to 7 bytes (.Net is 8 bytes) /// Notes: /// A DateTime containing only a date takes 3 bytes /// (except a .NET 2.0 Date with a specified DateTimeKind which will take a minimum /// of 5 bytes - no further optimization for this situation felt necessary since it /// is unlikely that a DateTimeKind would be specified without hh:mm also) /// Date plus hh:mm takes 5 bytes. /// Date plus hh:mm:ss takes 6 bytes. /// Date plus hh:mm:ss.fff takes 7 bytes. /// /// The DateTime value to store. Must not contain sub-millisecond data. public void WriteOptimized(DateTime value) { CheckOptimizable((value.Ticks%TimeSpan.TicksPerMillisecond) == 0, "Cannot optimize a DateTime with sub-millisecond accuracy"); var dateMask = new BitVector32(); dateMask[DateYearMask] = value.Year; dateMask[DateMonthMask] = value.Month; dateMask[DateDayMask] = value.Day; var initialData = (int) value.Kind; var writeAdditionalData = value != value.Date; writeAdditionalData |= initialData != 0; dateMask[DateHasTimeOrKindMask] = writeAdditionalData ? 1 : 0; // Store 3 bytes of Date information var dateMaskData = dateMask.Data; Write((byte) dateMaskData); Write((byte) (dateMaskData >> 8)); Write((byte) (dateMaskData >> 16)); if (writeAdditionalData) { CheckOptimizable(((value.Ticks%TimeSpan.TicksPerMillisecond) == 0), "Cannot optimize a DateTime with sub-millisecond accuracy"); EncodeTimeSpan(value.TimeOfDay, true, initialData); } } /// /// Writes a Decimal value into the stream using the fewest number of bytes possible. /// Stored Size: 1 byte to 14 bytes (.Net is 16 bytes) /// Restrictions: None /// /// The Decimal value to store public void WriteOptimized(Decimal value) { var data = Decimal.GetBits(value); var scale = (byte) (data[3] >> 16); byte flags = 0; if (scale != 0 && !PreserveDecimalScale && OptimizeForSize) { var normalized = Decimal.Truncate(value); if (normalized == value) { data = Decimal.GetBits(normalized); scale = 0; } } if ((data[3] & -2147483648) != 0) { flags |= 0x01; } if (scale != 0) { flags |= 0x02; } if (data[0] == 0) { flags |= 0x04; } else if (data[0] <= HighestOptimizable32BitValue && data[0] >= 0) { flags |= 0x20; } if (data[1] == 0) { flags |= 0x08; } else if (data[1] <= HighestOptimizable32BitValue && data[1] >= 0) { flags |= 0x40; } if (data[2] == 0) { flags |= 0x10; } else if (data[2] <= HighestOptimizable32BitValue && data[2] >= 0) { flags |= 0x80; } Write(flags); if (scale != 0) { Write(scale); } if ((flags & 0x04) == 0) { if ((flags & 0x20) != 0) { Write7BitEncodedSigned32BitValue(data[0]); } else { Write(data[0]); } } if ((flags & 0x08) == 0) { if ((flags & 0x40) != 0) { Write7BitEncodedSigned32BitValue(data[1]); } else { Write(data[1]); } } if ((flags & 0x10) == 0) { if ((flags & 0x80) != 0) { Write7BitEncodedSigned32BitValue(data[2]); } else { Write(data[2]); } } } /// /// Write an Int16 value using the fewest number of bytes possible. /// /// /// 0x0000 - 0x007f (0 to 127) takes 1 byte /// 0x0080 - 0x03FF (128 to 16,383) takes 2 bytes /// ---------------------------------------------------------------- /// 0x0400 - 0x7FFF (16,384 to 32,767) takes 3 bytes /// All negative numbers take 3 bytes /// /// Only call this method if the value is known to be between 0 and /// 16,383 otherwise use Write(Int16 value) /// /// The Int16 to store. Must be between 0 and 16,383 inclusive. public void WriteOptimized(short value) { CheckOptimizable(value < OptimizationFailure16BitValue && value >= 0, "Int16 value is not optimizable"); Write7BitEncodedSigned32BitValue(value); } /// /// Write an Int32 value using the fewest number of bytes possible. /// /// /// 0x00000000 - 0x0000007f (0 to 127) takes 1 byte /// 0x00000080 - 0x000003FF (128 to 16,383) takes 2 bytes /// 0x00000400 - 0x001FFFFF (16,384 to 2,097,151) takes 3 bytes /// 0x00200000 - 0x0FFFFFFF (2,097,152 to 268,435,455) takes 4 bytes /// ---------------------------------------------------------------- /// 0x10000000 - 0x07FFFFFF (268,435,456 and above) takes 5 bytes /// All negative numbers take 5 bytes /// /// Only call this method if the value is known to be between 0 and /// 268,435,455 otherwise use Write(Int32 value) /// /// The Int32 to store. Must be between 0 and 268,435,455 inclusive. public void WriteOptimized(int value) { CheckOptimizable(value < OptimizationFailure32BitValue && value >= 0, "Int32 value is not optimizable"); Write7BitEncodedSigned32BitValue(value); } /// /// Write an Int64 value using the fewest number of bytes possible. /// /// /// 0x0000000000000000 - 0x000000000000007f (0 to 127) takes 1 byte /// 0x0000000000000080 - 0x00000000000003FF (128 to 16,383) takes 2 bytes /// 0x0000000000000400 - 0x00000000001FFFFF (16,384 to 2,097,151) takes 3 bytes /// 0x0000000000200000 - 0x000000000FFFFFFF (2,097,152 to 268,435,455) takes 4 bytes /// 0x0000000010000000 - 0x00000007FFFFFFFF (268,435,456 to 34,359,738,367) takes 5 bytes /// 0x0000000800000000 - 0x000003FFFFFFFFFF (34,359,738,368 to 4,398,046,511,103) takes 6 bytes /// 0x0000040000000000 - 0x0001FFFFFFFFFFFF (4,398,046,511,104 to 562,949,953,421,311) takes 7 bytes /// 0x0002000000000000 - 0x00FFFFFFFFFFFFFF (562,949,953,421,312 to 72,057,594,037,927,935) takes 8 bytes /// ------------------------------------------------------------------ /// 0x0100000000000000 - 0x7FFFFFFFFFFFFFFF (72,057,594,037,927,936 to 9,223,372,036,854,775,807) takes 9 bytes /// 0x7FFFFFFFFFFFFFFF - 0xFFFFFFFFFFFFFFFF (9,223,372,036,854,775,807 and above) takes 10 bytes /// All negative numbers take 10 bytes /// /// Only call this method if the value is known to be between 0 and /// 72,057,594,037,927,935 otherwise use Write(Int64 value) /// /// The Int64 to store. Must be between 0 and 72,057,594,037,927,935 inclusive. public void WriteOptimized(long value) { CheckOptimizable(value < OptimizationFailure64BitValue && value >= 0, "long value is not optimizable"); Write7BitEncodedSigned64BitValue(value); } /// /// Writes a string value into the stream using the fewest number of bytes possible. /// Stored Size: 1 byte upwards depending on string length /// Notes: /// Encodes null, Empty, 'Y', 'N', ' ' values as a single byte /// Any other single char string is stored as two bytes /// All other strings are stored in a string token list: /// /// The TypeCode representing the current string token list is written first (1 byte), /// followed by the string token itself (1-4 bytes) /// /// When the current string list has reached 128 values then a new string list /// is generated and that is used for generating future string tokens. This continues /// until the maximum number (128) of string lists is in use, after which the string /// lists are used in a round-robin fashion. /// By doing this, more lists are created with fewer items which allows a smaller /// token size to be used for more strings. /// /// The first 16,384 strings will use a 1 byte token. /// The next 2,097,152 strings will use a 2 byte token. (This should suffice for most uses!) /// The next 268,435,456 strings will use a 3 byte token. (My, that is a lot!!) /// The next 34,359,738,368 strings will use a 4 byte token. (only shown for completeness!!!) /// /// The string to store. public void WriteOptimized(string value) { if (value == null) { WriteTypeCode(SerializedType.NullType); } else { if (value.Length == 1) { var singleChar = value[0]; switch (singleChar) { case 'Y': WriteTypeCode(SerializedType.YStringType); return; case 'N': WriteTypeCode(SerializedType.NStringType); return; case ' ': WriteTypeCode(SerializedType.SingleSpaceType); return; default: WriteTypeCode(SerializedType.SingleCharStringType); Write(singleChar); return; } } if (value.Length == 0) { WriteTypeCode(SerializedType.EmptyStringType); return; } int stringIndex; var isNew = stringLookup.Add(value, out stringIndex); Write((byte) (stringIndex%128)); Write7BitEncodedSigned32BitValue(stringIndex >> 7); if (isNew) { #if DEBUG var currentPosition = OutStream.CanSeek ? OutStream.Position : 0; #endif base.Write(value); #if DEBUG if (OutStream.CanSeek) { tableBytes += (int) (OutStream.Position - currentPosition); } #endif } } } /// /// Writes a TimeSpan value into the stream using the fewest number of bytes possible. /// Stored Size: 2 bytes to 8 bytes (.Net is 8 bytes) /// Notes: /// hh:mm (time) are always stored together and take 2 bytes. /// If seconds are present then 3 bytes unless (time) is not present in which case 2 bytes /// since the seconds are stored in the minutes position. /// If milliseconds are present then 4 bytes. /// In addition, if days are present they will add 1 to 4 bytes to the above. /// /// The TimeSpan value to store. Must not contain sub-millisecond data. public void WriteOptimized(TimeSpan value) { CheckOptimizable(((value.Ticks%TimeSpan.TicksPerMillisecond) == 0), "Cannot optimize a TimeSpan with sub-millisecond accuracy"); EncodeTimeSpan(value, false, 0); } /// /// Stores a non-null Type object into the stream. /// Stored Size: Depends on the length of the Type's name. /// If the type is a System type (mscorlib) then it is stored without assembly name information, /// otherwise the Type's AssemblyQualifiedName is used. /// /// The Type to store. Must not be null. public void WriteOptimized(Type value) { CheckOptimizable((value != null), "Cannot optimize a null Type"); WriteOptimized(value.AssemblyQualifiedName.IndexOf(", mscorlib,") == -1 ? value.AssemblyQualifiedName : value.FullName); } /// /// Write a UInt16 value using the fewest number of bytes possible. /// /// /// 0x0000 - 0x007f (0 to 127) takes 1 byte /// 0x0080 - 0x03FF (128 to 16,383) takes 2 bytes /// ---------------------------------------------------------------- /// 0x0400 - 0xFFFF (16,384 to 65,536) takes 3 bytes /// /// Only call this method if the value is known to be between 0 and /// 16,383 otherwise use Write(UInt16 value) /// /// The UInt16 to store. Must be between 0 and 16,383 inclusive. public void WriteOptimized(ushort value) { CheckOptimizable(value < OptimizationFailure16BitValue, "UInt16 value is not optimizable"); Write7BitEncodedUnsigned32BitValue(value); } /// /// Write a UInt32 value using the fewest number of bytes possible. /// /// /// 0x00000000 - 0x0000007f (0 to 127) takes 1 byte /// 0x00000080 - 0x000003FF (128 to 16,383) takes 2 bytes /// 0x00000400 - 0x001FFFFF (16,384 to 2,097,151) takes 3 bytes /// 0x00200000 - 0x0FFFFFFF (2,097,152 to 268,435,455) takes 4 bytes /// ---------------------------------------------------------------- /// 0x10000000 - 0xFFFFFFFF (268,435,456 and above) takes 5 bytes /// /// Only call this method if the value is known to be between 0 and /// 268,435,455 otherwise use Write(UInt32 value) /// /// The UInt32 to store. Must be between 0 and 268,435,455 inclusive. public void WriteOptimized(uint value) { CheckOptimizable(value < OptimizationFailure32BitValue, "UInt32 value is not optimizable"); Write7BitEncodedUnsigned32BitValue(value); } /// /// Write a UInt64 value using the fewest number of bytes possible. /// /// /// 0x0000000000000000 - 0x000000000000007f (0 to 127) takes 1 byte /// 0x0000000000000080 - 0x00000000000003FF (128 to 16,383) takes 2 bytes /// 0x0000000000000400 - 0x00000000001FFFFF (16,384 to 2,097,151) takes 3 bytes /// 0x0000000000200000 - 0x000000000FFFFFFF (2,097,152 to 268,435,455) takes 4 bytes /// 0x0000000010000000 - 0x00000007FFFFFFFF (268,435,456 to 34,359,738,367) takes 5 bytes /// 0x0000000800000000 - 0x000003FFFFFFFFFF (34,359,738,368 to 4,398,046,511,103) takes 6 bytes /// 0x0000040000000000 - 0x0001FFFFFFFFFFFF (4,398,046,511,104 to 562,949,953,421,311) takes 7 bytes /// 0x0002000000000000 - 0x00FFFFFFFFFFFFFF (562,949,953,421,312 to 72,057,594,037,927,935) takes 8 bytes /// ------------------------------------------------------------------ /// 0x0100000000000000 - 0x7FFFFFFFFFFFFFFF (72,057,594,037,927,936 to 9,223,372,036,854,775,807) takes 9 bytes /// 0x7FFFFFFFFFFFFFFF - 0xFFFFFFFFFFFFFFFF (9,223,372,036,854,775,807 and above) takes 10 bytes /// /// Only call this method if the value is known to be between 0 and /// 72,057,594,037,927,935 otherwise use Write(UInt64 value) /// /// The UInt64 to store. Must be between 0 and 72,057,594,037,927,935 inclusive. public void WriteOptimized(ulong value) { CheckOptimizable(value < OptimizationFailure64BitValue, "ulong value is not optimizable"); Write7BitEncodedUnsigned64BitValue(value); } /// /// Writes a Boolean[] into the stream. /// Notes: /// A null or empty array will take 1 byte. /// Calls WriteOptimized(Boolean[]). /// /// The Boolean[] to store. public void Write(bool[] values) { WriteOptimized(values); } /// /// Writes a Byte[] into the stream. /// Notes: /// A null or empty array will take 1 byte. /// /// The Byte[] to store. public override void Write(byte[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteTypeCode(SerializedType.NonOptimizedTypedArrayType); WriteArray(values); } } /// /// Writes a Char[] into the stream. /// Notes: /// A null or empty array will take 1 byte. /// /// The Char[] to store. public override void Write(char[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteTypeCode(SerializedType.NonOptimizedTypedArrayType); WriteArray(values); } } /// /// Writes a DateTime[] into the stream. /// Notes: /// A null or empty array will take 1 byte. /// /// The DateTime[] to store. public void Write(DateTime[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteArray(values, null); } } /// /// Writes a Decimal[] into the stream. /// Notes: /// A null or empty array will take 1 byte. /// Calls WriteOptimized(Decimal[]). /// /// The Decimal[] to store. public void Write(decimal[] values) { WriteOptimized(values); } /// /// Writes a Double[] into the stream. /// Notes: /// A null or empty array will take 1 byte. /// /// The Double[] to store. public void Write(double[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteTypeCode(SerializedType.NonOptimizedTypedArrayType); WriteArray(values); } } /// /// Writes a Single[] into the stream. /// Notes: /// A null or empty array will take 1 byte. /// /// The Single[] to store. public void Write(float[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteTypeCode(SerializedType.NonOptimizedTypedArrayType); WriteArray(values); } } /// /// Writes a Guid[] into the stream. /// Notes: /// A null or empty array will take 1 byte. /// /// The Guid[] to store. public void Write(Guid[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteTypeCode(SerializedType.NonOptimizedTypedArrayType); WriteArray(values); } } /// /// Writes an Int32[] into the stream. /// Notes: /// A null or empty array will take 1 byte. /// /// The Int32[] to store. public void Write(int[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteArray(values, null); } } /// /// Writes an Int64[] into the stream. /// Notes: /// A null or empty array will take 1 byte. /// /// The Int64[] to store. public void Write(long[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteArray(values, null); } } /// /// Writes an object[] into the stream. /// Stored Size: 2 bytes upwards depending on data content /// Notes: /// A null object[] takes 1 byte. /// An empty object[] takes 2 bytes. /// The contents of the array will be stored optimized. /// /// The object[] to store. public void Write(object[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyObjectArrayType); } else { WriteTypeCode(SerializedType.ObjectArrayType); WriteObjectArray(values); } } /// /// Writes an SByte[] into the stream. /// Notes: /// A null or empty array will take 1 byte. /// /// The SByte[] to store. public void Write(sbyte[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteTypeCode(SerializedType.NonOptimizedTypedArrayType); WriteArray(values); } } /// /// Writes an Int16[]or a null into the stream. /// Notes: /// A null or empty array will take 1 byte. /// Calls WriteOptimized(decimal[]). /// /// The Int16[] to store. public void Write(short[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteTypeCode(SerializedType.NonOptimizedTypedArrayType); WriteArray(values); } } /// /// Writes a TimeSpan[] into the stream. /// Notes: /// A null or empty array will take 1 byte. /// /// The TimeSpan[] to store. public void Write(TimeSpan[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteArray(values, null); } } /// /// Writes a UInt32[] into the stream. /// Notes: /// A null or empty array will take 1 byte. /// /// The UInt32[] to store. public void Write(uint[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteArray(values, null); } } /// /// Writes a UInt64[] into the stream. /// Notes: /// A null or empty array will take 1 byte. /// /// The UInt64[] to store. public void Write(ulong[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteArray(values, null); } } /// /// Writes a UInt16[] into the stream. /// Notes: /// A null or empty array will take 1 byte. /// /// The UInt16[] to store. public void Write(ushort[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteTypeCode(SerializedType.NonOptimizedTypedArrayType); WriteArray(values); } } /// /// Writes an optimized Boolean[] into the stream using the fewest possible bytes. /// Notes: /// A null or empty array will take 1 byte. /// Stored as a BitArray. /// /// The Boolean[] to store. public void WriteOptimized(bool[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteTypeCode(SerializedType.FullyOptimizedTypedArrayType); WriteArray(values); } } /// /// Writes a DateTime[] into the stream using the fewest possible bytes. /// Notes: /// A null or empty array will take 1 byte. /// /// The DateTime[] to store. public void WriteOptimized(DateTime[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { BitArray optimizeFlags = null; var notOptimizable = 0; var notWorthOptimizingLimit = 1 + (int) (values.Length*(OptimizeForSize ? 0.8f : 0.6f)); for (var i = 0; (i < values.Length) && (notOptimizable < notWorthOptimizingLimit); i++) { if (values[i].Ticks%TimeSpan.TicksPerMillisecond != 0) { notOptimizable++; } else { if (optimizeFlags == null) { optimizeFlags = new BitArray(values.Length); } optimizeFlags[i] = true; } } if (notOptimizable == 0) { optimizeFlags = FullyOptimizableTypedArray; } else if (notOptimizable >= notWorthOptimizingLimit) { optimizeFlags = null; } WriteArray(values, optimizeFlags); } } /// /// Writes a Decimal[] into the stream using the fewest possible bytes. /// Notes: /// A null or empty array will take 1 byte. /// /// The Decimal[] to store. public void WriteOptimized(decimal[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { WriteTypeCode(SerializedType.FullyOptimizedTypedArrayType); WriteArray(values); } } /// /// Writes a not-null object[] into the stream using the fewest number of bytes possible. /// Stored Size: 2 bytes upwards depending on data content /// Notes: /// An empty object[] takes 1 byte. /// The contents of the array will be stored optimized. /// /// The object[] to store. Must not be null. public void WriteOptimized(object[] values) { CheckOptimizable(values != null, "Cannot optimize a null object[]"); WriteObjectArray(values); } /// /// Writes a pair of object[] arrays into the stream using the fewest number of bytes possible. /// The arrays must not be null and must have the same length /// The first array's values are written optimized /// The second array's values are compared against the first and, where identical, will be stored /// using a single byte. /// Useful for storing entity data where there is a before-change and after-change set of value pairs /// and, typically, only a few of the values will have changed. /// /// The first object[] value which must not be null and must have the same length as values2 /// The second object[] value which must not be null and must have the same length as values1 public void WriteOptimized(object[] values1, object[] values2) { CheckOptimizable(((values1 != null) && (values2 != null)), "Cannot optimimize an object[] pair that is null"); CheckOptimizable((values1.Length == values2.Length), "Cannot optimize an object[] pair with different lengths"); WriteObjectArray(values1); var lastIndex = values2.Length - 1; for (var i = 0; i < values2.Length; i++) { var value2 = values2[i]; // if value2 is null, we've to check if values1 is null, otherwise we've to compare value2 with the value at the same spot in values1 if (value2 == null ? values1[i] == null : value2.Equals(values1[i])) { var duplicates = 0; for (; i < lastIndex && (values2[i + 1] == null ? values1[i + 1] == null : values2[i + 1].Equals(values1[i + 1])); i++) { duplicates++; } if (duplicates == 0) { WriteTypeCode(SerializedType.DuplicateValueType); } else { WriteTypeCode(SerializedType.DuplicateValueSequenceType); Write7BitEncodedSigned32BitValue(duplicates); } } else if (value2 == null) { var duplicates = 0; for (; i < lastIndex && values2[i + 1] == null; i++) { duplicates++; } if (duplicates == 0) { WriteTypeCode(SerializedType.NullType); } else { WriteTypeCode(SerializedType.NullSequenceType); Write7BitEncodedSigned32BitValue(duplicates); } } else { if (value2 == DBNull.Value) { var duplicates = 0; for (; i < lastIndex && values2[i + 1] == DBNull.Value; i++) { duplicates++; } if (duplicates == 0) { WriteTypeCode(SerializedType.DBNullType); } else { WriteTypeCode(SerializedType.DBNullSequenceType); Write7BitEncodedSigned32BitValue(duplicates); } } else { WriteObject(value2); } } } } /// /// Writes an Int16[] into the stream using the fewest possible bytes. /// Notes: /// A null or empty array will take 1 byte. /// /// The Int16[] to store. public void WriteOptimized(short[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { BitArray optimizeFlags = null; var notOptimizable = 0; var notWorthOptimizingLimit = 1 + (int) (values.Length*(OptimizeForSize ? 0.8f : 0.6f)); for (var i = 0; i < values.Length && notOptimizable < notWorthOptimizingLimit; i++) { if (values[i] < 0 || values[i] > HighestOptimizable16BitValue) { notOptimizable++; } else { if (optimizeFlags == null) { optimizeFlags = new BitArray(values.Length); } optimizeFlags[i] = true; } } if (notOptimizable == 0) { optimizeFlags = FullyOptimizableTypedArray; } else if (notOptimizable >= notWorthOptimizingLimit) { optimizeFlags = null; } WriteArray(values, optimizeFlags); } } /// /// Writes an Int32[] into the stream using the fewest possible bytes. /// Notes: /// A null or empty array will take 1 byte. /// /// The Int32[] to store. public void WriteOptimized(int[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { BitArray optimizeFlags = null; var notOptimizable = 0; var notWorthOptimizingLimit = 1 + (int) (values.Length*(OptimizeForSize ? 0.8f : 0.6f)); for (var i = 0; i < values.Length && notOptimizable < notWorthOptimizingLimit; i++) { if (values[i] < 0 || values[i] > HighestOptimizable32BitValue) { notOptimizable++; } else { if (optimizeFlags == null) { optimizeFlags = new BitArray(values.Length); } optimizeFlags[i] = true; } } if (notOptimizable == 0) { optimizeFlags = FullyOptimizableTypedArray; } else if (notOptimizable >= notWorthOptimizingLimit) { optimizeFlags = null; } WriteArray(values, optimizeFlags); } } /// /// Writes an Int64[] into the stream using the fewest possible bytes. /// Notes: /// A null or empty array will take 1 byte. /// /// The Int64[] to store. public void WriteOptimized(long[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { BitArray optimizeFlags = null; var notOptimizable = 0; var notWorthOptimizingLimit = 1 + (int) (values.Length*(OptimizeForSize ? 0.8f : 0.6f)); for (var i = 0; i < values.Length && notOptimizable < notWorthOptimizingLimit; i++) { if (values[i] < 0 || values[i] > HighestOptimizable64BitValue) { notOptimizable++; } else { if (optimizeFlags == null) { optimizeFlags = new BitArray(values.Length); } optimizeFlags[i] = true; } } if (notOptimizable == 0) { optimizeFlags = FullyOptimizableTypedArray; } else if (notOptimizable >= notWorthOptimizingLimit) { optimizeFlags = null; } WriteArray(values, optimizeFlags); } } /// /// Writes a TimeSpan[] into the stream using the fewest possible bytes. /// Notes: /// A null or empty array will take 1 byte. /// /// The TimeSpan[] to store. public void WriteOptimized(TimeSpan[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { BitArray optimizeFlags = null; var notOptimizable = 0; var notWorthOptimizingLimit = 1 + (int) (values.Length*(OptimizeForSize ? 0.8f : 0.6f)); for (var i = 0; i < values.Length && notOptimizable < notWorthOptimizingLimit; i++) { if (values[i].Ticks%TimeSpan.TicksPerMillisecond != 0) { notOptimizable++; } else { if (optimizeFlags == null) { optimizeFlags = new BitArray(values.Length); } optimizeFlags[i] = true; } } if (notOptimizable == 0) { optimizeFlags = FullyOptimizableTypedArray; } else if (notOptimizable >= notWorthOptimizingLimit) { optimizeFlags = null; } WriteArray(values, optimizeFlags); } } /// /// Writes a UInt16[] into the stream using the fewest possible bytes. /// Notes: /// A null or empty array will take 1 byte. /// /// The UInt16[] to store. public void WriteOptimized(ushort[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { BitArray optimizeFlags = null; var notOptimizable = 0; var notWorthOptimizingLimit = 1 + (int) (values.Length*(OptimizeForSize ? 0.8f : 0.6f)); for (var i = 0; i < values.Length && notOptimizable < notWorthOptimizingLimit; i++) { if (values[i] > HighestOptimizable16BitValue) { notOptimizable++; } else { if (optimizeFlags == null) { optimizeFlags = new BitArray(values.Length); } optimizeFlags[i] = true; } } if (notOptimizable == 0) { optimizeFlags = FullyOptimizableTypedArray; } else if (notOptimizable >= notWorthOptimizingLimit) { optimizeFlags = null; } WriteArray(values, optimizeFlags); } } /// /// Writes a UInt32[] into the stream using the fewest possible bytes. /// Notes: /// A null or empty array will take 1 byte. /// /// The UInt32[] to store. public void WriteOptimized(uint[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { BitArray optimizeFlags = null; var notOptimizable = 0; var notWorthOptimizingLimit = 1 + (int) (values.Length*(OptimizeForSize ? 0.8f : 0.6f)); for (var i = 0; i < values.Length && notOptimizable < notWorthOptimizingLimit; i++) { if (values[i] > HighestOptimizable32BitValue) { notOptimizable++; } else { if (optimizeFlags == null) { optimizeFlags = new BitArray(values.Length); } optimizeFlags[i] = true; } } if (notOptimizable == 0) { optimizeFlags = FullyOptimizableTypedArray; } else if (notOptimizable >= notWorthOptimizingLimit) { optimizeFlags = null; } WriteArray(values, optimizeFlags); } } /// /// Writes a UInt64[] into the stream using the fewest possible bytes. /// Notes: /// A null or empty array will take 1 byte. /// /// The UInt64[] to store. public void WriteOptimized(ulong[] values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else if (values.Length == 0) { WriteTypeCode(SerializedType.EmptyTypedArrayType); } else { BitArray optimizeFlags = null; var notOptimizable = 0; var notWorthOptimizingLimit = 1 + (int) (values.Length*(OptimizeForSize ? 0.8f : 0.6f)); for (var i = 0; i < values.Length && notOptimizable < notWorthOptimizingLimit; i++) { if (values[i] > HighestOptimizable64BitValue) { notOptimizable++; } else { if (optimizeFlags == null) { optimizeFlags = new BitArray(values.Length); } optimizeFlags[i] = true; } } if (notOptimizable == 0) { optimizeFlags = FullyOptimizableTypedArray; } else if (notOptimizable >= notWorthOptimizingLimit) { optimizeFlags = null; } WriteArray(values, optimizeFlags); } } /// /// Writes a Nullable type into the stream. /// Synonym for WriteObject(). /// /// The Nullable value to store. public void WriteNullable(ValueType value) { WriteObject(value); } /// /// Writes a non-null generic Dictionary into the stream. /// /// /// The key and value types themselves are not stored - they must be /// supplied at deserialization time. /// /// An array of keys is stored followed by an array of values. /// /// The key Type. /// The value Type. /// The generic dictionary. public void Write(Dictionary value) { var keys = new K[value.Count]; value.Keys.CopyTo(keys, 0); var values = new V[value.Count]; value.Values.CopyTo(values, 0); WriteTypedArray(keys, false); WriteTypedArray(values, false); } /// /// Writes a non-null generic List into the stream. /// /// /// The list type itself is not stored - it must be supplied /// at deserialization time. /// /// The list contents are stored as an array. /// /// The list Type. /// The generic List. public void Write(List value) { WriteTypedArray(value.ToArray(), false); } /// /// Writes a null or a typed array into the stream. /// /// The array to store. public void WriteTypedArray(Array values) { if (values == null) { WriteTypeCode(SerializedType.NullType); } else { WriteTypedArray(values, true); } } /// /// Updates the header to store /// 1) The total length of serialized data /// 2) The number of string tokens /// 3) The number of object tokens /// /// Does nothing if the stream is not seekable or the constructor /// specified not to update the header. /// /// Notes: /// Called automatically by ToArray() otherwise must be called /// manually when serialization is complete. /// /// The total length of serialized data or 0 if the stream is not seekable public int UpdateHeader() { var result = BaseStream.CanSeek ? (int) BaseStream.Position - startPosition : 0; // BaseStream.CanSeek is implied if allowUpdateHeader is true if (allowUpdateHeader) { var currentPosition = BaseStream.Position; BaseStream.Position = startPosition; Write(result); Write(stringLookup.Count); Write(objectTokenLookup.Count); BaseStream.Position = currentPosition; } return result; } /// /// Returns a byte[] containing all of the serialized data /// where the underlying stream is a MemoryStream. /// /// Only call this method once all of the data has been serialized. /// /// /// A byte[] containing all serialized data. public byte[] ToArray() { var memoryStream = BaseStream as MemoryStream; if (memoryStream == null) { throw new InvalidOperationException("Cannot call ToArray() where the underlying stream is not a MemoryStream"); } UpdateHeader(); if (startPosition == 0) { return memoryStream.ToArray(); } var length = (int) memoryStream.Position - startPosition; var result = new byte[length]; var currentPosition = BaseStream.Position; BaseStream.Position = startPosition; memoryStream.Read(result, 0, length); BaseStream.Position = currentPosition; return result; } /// /// Writes a byte[] directly into the stream. /// The size of the array is not stored so only use this method when /// the number of bytes will be known at deserialization time. /// /// A null value will throw an exception /// /// The byte[] to store. Must not be null. public void WriteBytesDirect(byte[] value) { base.Write(value); } /// /// Writes a non-null string directly to the stream without tokenization. /// /// The string to store. Must not be null. public void WriteStringDirect(string value) { CheckOptimizable(value != null, "Cannot directly write a null string"); base.Write(value); } /// /// Writes a token (an Int32 taking 1 to 4 bytes) into the stream that represents the object instance. /// The same token will always be used for the same object instance. /// /// The object will be serialized once and recreated at deserialization time. /// Calls to SerializationReader.ReadTokenizedObject() will retrieve the same object instance. /// /// /// The object to tokenize. Must not be null and must not be a string. public void WriteTokenizedObject(object value) { WriteTokenizedObject(value, false); } /// /// Writes a token (an Int32 taking 1 to 4 bytes) into the stream that represents the object instance. /// The same token will always be used for the same object instance. /// /// When recreateFromType is set to true, the object's Type will be stored and the object recreated using /// Activator.GetInstance with a parameterless contructor. This is useful for stateless, factory-type classes. /// /// When recreateFromType is set to false, the object will be serialized once and recreated at deserialization time. /// /// Calls to SerializationReader.ReadTokenizedObject() will retrieve the same object instance. /// /// The object to tokenize. Must not be null and must not be a string. /// true if the object can be recreated using a parameterless constructor; /// false if the object should be serialized as-is public void WriteTokenizedObject(object value, bool recreateFromType) { CheckOptimizable(value != null, "Cannot write a null tokenized object"); CheckOptimizable(!(value is string), "Use Write(string) instead of WriteTokenizedObject()"); if (recreateFromType) { CheckOptimizable(HasEmptyConstructor(value.GetType()), "Cannot recreate type is it doesn't have a default/empty constructor"); value = new SingletonTypeWrapper(value); } var token = objectTokenLookup[value]; if (token != null) { Write7BitEncodedSigned32BitValue((int) token); } else { var newToken = objectTokenLookup.Count; objectTokenLookup[value] = newToken; Write7BitEncodedSigned32BitValue(newToken); #if DEBUG var currentPosition = OutStream.Position; #endif WriteObject(value); #if DEBUG tableBytes += (int) (OutStream.Position - currentPosition); #endif } } /// /// Finds the surrogate type for the type passed in. /// /// The type. /// internal static IFastSerializationTypeSurrogate FindSurrogateForType(Type type) { foreach (var surrogate in TypeSurrogates) { if (surrogate.SupportsType(type)) { return surrogate; } } return null; } /// /// Creates the binary formatter. /// /// private static BinaryFormatter CreateBinaryFormatter() { return new BinaryFormatter { AssemblyFormat = FormatterAssemblyStyle.Full }; } /// /// Encodes a TimeSpan into the fewest number of bytes. /// Has been separated from the WriteOptimized(TimeSpan) method so that WriteOptimized(DateTime) /// can also use this for .NET 2.0 DateTimeKind information. /// By taking advantage of the fact that a DateTime's TimeOfDay portion will never use the IsNegative /// and HasDays flags, we can use these 2 bits to store the DateTimeKind and, since DateTimeKind is /// unlikely to be set without a Time, we need no additional bytes to support a .NET 2.0 DateTime. /// /// The TimeSpan to store. /// True if the TimeSpan is the TimeOfDay from a DateTime; False if a real TimeSpan. /// The intial data for the BitVector32 - contains DateTimeKind or 0 private void EncodeTimeSpan(TimeSpan value, bool partOfDateTime, int initialData) { var packedData = new BitVector32(initialData); int days; var hours = Math.Abs(value.Hours); var minutes = Math.Abs(value.Minutes); var seconds = Math.Abs(value.Seconds); var milliseconds = Math.Abs(value.Milliseconds); var hasTime = hours != 0 || minutes != 0; var optionalBytes = 0; if (partOfDateTime) { days = 0; } else { days = Math.Abs(value.Days); packedData[IsNegativeSection] = value.Ticks < 0 ? 1 : 0; packedData[HasDaysSection] = days != 0 ? 1 : 0; } if (hasTime) { packedData[HasTimeSection] = 1; packedData[HoursSection] = hours; packedData[MinutesSection] = minutes; } if (seconds != 0) { packedData[HasSecondsSection] = 1; if (!hasTime && (milliseconds == 0)) // If only seconds are present then we can use the minutes slot to save a byte { packedData[MinutesSection] = seconds; } else { packedData[SecondsSection] = seconds; optionalBytes++; } } if (milliseconds != 0) { packedData[HasMillisecondsSection] = 1; packedData[MillisecondsSection] = milliseconds; optionalBytes = 2; } var data = packedData.Data; Write((byte) data); Write((byte) (data >> 8)); // Always write minimum of two bytes if (optionalBytes > 0) { Write((byte) (data >> 16)); } if (optionalBytes > 1) { Write((byte) (data >> 24)); } if (days != 0) { Write7BitEncodedSigned32BitValue(days); } } /// /// Checks whether an optimization condition has been met and throw an exception if not. /// /// This method has been made conditional on THROW_IF_NOT_OPTIMIZABLE being set at compile time. /// By default, this isn't set but could be set explicitly if exceptions are required and /// the evaluation overhead is acceptable. /// If not set, then this method and all references to it are removed at compile time. /// /// Leave at the default for optimum usage. /// /// An expression evaluating to true if the optimization condition is met, false otherwise. /// The message to include in the exception should the optimization condition not be met. [Conditional("THROW_IF_NOT_OPTIMIZABLE")] private static void CheckOptimizable(bool condition, string message) { if (!condition) { throw new InvalidOperationException(message); } } /// /// Stores a 32-bit signed value into the stream using 7-bit encoding. /// /// The value is written 7 bits at a time (starting with the least-significant bits) until there are no more bits to write. /// The eighth bit of each byte stored is used to indicate whether there are more bytes following this one. /// /// See Write(Int32) for details of the values that are optimizable. /// /// The Int32 value to encode. private void Write7BitEncodedSigned32BitValue(int value) { var unsignedValue = unchecked((uint) value); while (unsignedValue >= 0x80) { Write((byte) (unsignedValue | 0x80)); unsignedValue >>= 7; } Write((byte) unsignedValue); } /// /// Stores a 64-bit signed value into the stream using 7-bit encoding. /// /// The value is written 7 bits at a time (starting with the least-significant bits) until there are no more bits to write. /// The eighth bit of each byte stored is used to indicate whether there are more bytes following this one. /// /// See Write(Int64) for details of the values that are optimizable. /// /// The Int64 value to encode. private void Write7BitEncodedSigned64BitValue(long value) { var unsignedValue = unchecked((ulong) value); while (unsignedValue >= 0x80) { Write((byte) (unsignedValue | 0x80)); unsignedValue >>= 7; } Write((byte) unsignedValue); } /// /// Stores a 32-bit unsigned value into the stream using 7-bit encoding. /// /// The value is written 7 bits at a time (starting with the least-significant bits) until there are no more bits to write. /// The eighth bit of each byte stored is used to indicate whether there are more bytes following this one. /// /// See Write(UInt32) for details of the values that are optimizable. /// /// The UInt32 value to encode. private void Write7BitEncodedUnsigned32BitValue(uint value) { while (value >= 0x80) { Write((byte) (value | 0x80)); value >>= 7; } Write((byte) value); } /// /// Stores a 64-bit unsigned value into the stream using 7-bit encoding. /// /// The value is written 7 bits at a time (starting with the least-significant bits) until there are no more bits to write. /// The eighth bit of each byte stored is used to indicate whether there are more bytes following this one. /// /// See Write(ULong) for details of the values that are optimizable. /// /// The ULong value to encode. private void Write7BitEncodedUnsigned64BitValue(ulong value) { while (value >= 0x80) { Write((byte) (value | 0x80)); value >>= 7; } Write((byte) value); } /// /// Internal implementation to store a non-null Boolean[]. /// /// /// Stored as a BitArray for optimization. /// /// The Boolean[] to store. private void WriteArray(bool[] values) { WriteOptimized(new BitArray(values)); } /// /// Internal implementation to store a non-null Byte[]. /// /// The Byte[] to store. private void WriteArray(byte[] values) { Write7BitEncodedSigned32BitValue(values.Length); if (values.Length > 0) { base.Write(values); } } /// /// Internal implementation to store a non-null Char[]. /// /// The Char[] to store. private void WriteArray(char[] values) { Write7BitEncodedSigned32BitValue(values.Length); if (values.Length > 0) { base.Write(values); } } /// /// Internal implementation to write a non, null DateTime[] using a BitArray to /// determine which elements are optimizable. /// /// The DateTime[] to store. /// A BitArray indicating which of the elements which are optimizable; /// a reference to constant FullyOptimizableValueArray if all the elements are optimizable; or null /// if none of the elements are optimizable. private void WriteArray(DateTime[] values, BitArray optimizeFlags) { WriteTypedArrayTypeCode(optimizeFlags, values.Length); for (var i = 0; i < values.Length; i++) { if ((optimizeFlags == null) || ((optimizeFlags != FullyOptimizableTypedArray) && !optimizeFlags[i])) { Write(values[i]); } else { WriteOptimized(values[i]); } } } /// /// Internal implementation to store a non-null Decimal[]. /// /// /// All elements are stored optimized. /// /// The Decimal[] to store. private void WriteArray(decimal[] values) { Write7BitEncodedSigned32BitValue(values.Length); for (var i = 0; i < values.Length; i++) { WriteOptimized(values[i]); } } /// /// Internal implementation to store a non-null Double[]. /// /// The Double[] to store. private void WriteArray(double[] values) { Write7BitEncodedSigned32BitValue(values.Length); foreach (var value in values) { Write(value); } } /// /// Internal implementation to store a non-null Single[]. /// /// The Single[] to store. private void WriteArray(float[] values) { Write7BitEncodedSigned32BitValue(values.Length); foreach (var value in values) { Write(value); } } /// /// Internal implementation to store a non-null Guid[]. /// /// The Guid[] to store. private void WriteArray(Guid[] values) { Write7BitEncodedSigned32BitValue(values.Length); foreach (var value in values) { Write(value); } } /// /// Internal implementation to write a non-null Int16[] using a BitArray to determine which elements are optimizable. /// /// The Int16[] to store. /// A BitArray indicating which of the elements which are optimizable; /// a reference to constant FullyOptimizableValueArray if all the elements are optimizable; or null /// if none of the elements are optimizable. private void WriteArray(short[] values, BitArray optimizeFlags) { WriteTypedArrayTypeCode(optimizeFlags, values.Length); for (var i = 0; i < values.Length; i++) { if ((optimizeFlags == null) || ((optimizeFlags != FullyOptimizableTypedArray) && !optimizeFlags[i])) { Write(values[i]); } else { Write7BitEncodedSigned32BitValue(values[i]); } } } /// /// Internal implementation to write a non-null Int32[] using a BitArray to determine which elements are optimizable. /// /// The Int32[] to store. /// A BitArray indicating which of the elements which are optimizable; /// a reference to constant FullyOptimizableValueArray if all the elements are optimizable; or null /// if none of the elements are optimizable. private void WriteArray(int[] values, BitArray optimizeFlags) { WriteTypedArrayTypeCode(optimizeFlags, values.Length); for (var i = 0; i < values.Length; i++) { if ((optimizeFlags == null) || ((optimizeFlags != FullyOptimizableTypedArray) && !optimizeFlags[i])) { Write(values[i]); } else { Write7BitEncodedSigned32BitValue(values[i]); } } } /// /// Internal implementation to writes a non-null Int64[] using a BitArray to determine which elements are optimizable. /// /// The Int64[] to store. /// A BitArray indicating which of the elements which are optimizable; /// a reference to constant FullyOptimizableValueArray if all the elements are optimizable; or null /// if none of the elements are optimizable. private void WriteArray(long[] values, BitArray optimizeFlags) { WriteTypedArrayTypeCode(optimizeFlags, values.Length); for (var i = 0; i < values.Length; i++) { if ((optimizeFlags == null) || ((optimizeFlags != FullyOptimizableTypedArray) && !optimizeFlags[i])) { Write(values[i]); } else { Write7BitEncodedSigned64BitValue(values[i]); } } } /// /// Internal implementation to store a non-null SByte[]. /// /// The SByte[] to store. private void WriteArray(sbyte[] values) { Write7BitEncodedSigned32BitValue(values.Length); foreach (var value in values) { Write(value); } } /// /// Internal implementation to store a non-null Int16[]. /// /// The Int16[] to store. private void WriteArray(short[] values) { Write7BitEncodedSigned32BitValue(values.Length); foreach (var value in values) { Write(value); } } /// /// Internal implementation to write a non-null TimeSpan[] using a BitArray to determine which elements are optimizable. /// /// The TimeSpan[] to store. /// A BitArray indicating which of the elements which are optimizable; /// a reference to constant FullyOptimizableValueArray if all the elements are optimizable; or null /// if none of the elements are optimizable. private void WriteArray(TimeSpan[] values, BitArray optimizeFlags) { WriteTypedArrayTypeCode(optimizeFlags, values.Length); for (var i = 0; i < values.Length; i++) { if ((optimizeFlags == null) || ((optimizeFlags != FullyOptimizableTypedArray) && !optimizeFlags[i])) { Write(values[i]); } else { WriteOptimized(values[i]); } } } /// /// Internal implementation to write a non-null UInt16[] using a BitArray to determine which elements are optimizable. /// /// The UInt16[] to store. /// A BitArray indicating which of the elements which are optimizable; /// a reference to constant FullyOptimizableValueArray if all the elements are optimizable; or null /// if none of the elements are optimizable. private void WriteArray(ushort[] values, BitArray optimizeFlags) { WriteTypedArrayTypeCode(optimizeFlags, values.Length); for (var i = 0; i < values.Length; i++) { if ((optimizeFlags == null) || ((optimizeFlags != FullyOptimizableTypedArray) && !optimizeFlags[i])) { Write(values[i]); } else { Write7BitEncodedUnsigned32BitValue(values[i]); } } } /// /// Internal implementation to write a non-null UInt32[] using a BitArray to determine which elements are optimizable. /// /// The UInt32[] to store. /// A BitArray indicating which of the elements which are optimizable; /// a reference to constant FullyOptimizableValueArray if all the elements are optimizable; or null /// if none of the elements are optimizable. private void WriteArray(uint[] values, BitArray optimizeFlags) { WriteTypedArrayTypeCode(optimizeFlags, values.Length); for (var i = 0; i < values.Length; i++) { if ((optimizeFlags == null) || ((optimizeFlags != FullyOptimizableTypedArray) && !optimizeFlags[i])) { Write(values[i]); } else { Write7BitEncodedUnsigned32BitValue(values[i]); } } } /// /// Internal implementation to store a non-null UInt16[]. /// /// The UIn16[] to store. private void WriteArray(ushort[] values) { Write7BitEncodedSigned32BitValue(values.Length); foreach (var value in values) { Write(value); } } /// /// Internal implementation to write a non-null UInt64[] using a BitArray to determine which elements are optimizable. /// /// The UInt64[] to store. /// A BitArray indicating which of the elements which are optimizable; /// a reference to constant FullyOptimizableValueArray if all the elements are optimizable; or null /// if none of the elements are optimizable. private void WriteArray(ulong[] values, BitArray optimizeFlags) { WriteTypedArrayTypeCode(optimizeFlags, values.Length); for (var i = 0; i < values.Length; i++) { if ((optimizeFlags == null) || ((optimizeFlags != FullyOptimizableTypedArray) && !optimizeFlags[i])) { Write(values[i]); } else { Write7BitEncodedUnsigned64BitValue(values[i]); } } } /// /// Writes the values in the non-null object[] into the stream. /// /// Sequences of null values and sequences of DBNull.Values are stored with a flag and optimized count. /// Other values are stored using WriteObject(). /// /// This routine is called by the Write(object[]), WriteOptimized(object[]) and Write(object[], object[])) methods. /// /// private void WriteObjectArray(object[] values) { Write7BitEncodedSigned32BitValue(values.Length); var lastIndex = values.Length - 1; for (var i = 0; i < values.Length; i++) { var value = values[i]; if ((i < lastIndex) && (value == null ? values[i + 1] == null : value.Equals(values[i + 1]))) { var duplicates = 1; if (value == null) { WriteTypeCode(SerializedType.NullSequenceType); for (i++; i < lastIndex && values[i + 1] == null; i++) { duplicates++; } } else if (value == DBNull.Value) { WriteTypeCode(SerializedType.DBNullSequenceType); for (i++; i < lastIndex && values[i + 1] == DBNull.Value; i++) { duplicates++; } } else { WriteTypeCode(SerializedType.DuplicateValueSequenceType); for (i++; i < lastIndex && value.Equals(values[i + 1]); i++) { duplicates++; } WriteObject(value); } Write7BitEncodedSigned32BitValue(duplicates); } else { WriteObject(value); } } } /// /// Stores the specified SerializedType code into the stream. /// /// By using a centralized method, it is possible to collect statistics for the /// type of data being stored in DEBUG mode. /// /// Use the DumpTypeUsage() method to show a list of used SerializedTypes and /// the number of times each has been used. This method and the collection code /// will be optimized out when compiling in Release mode. /// /// The SerializedType to store. private void WriteTypeCode(SerializedType typeCode) { Write((byte) typeCode); #if DEBUG typeUsage[(int) typeCode]++; #endif } /// /// Internal implementation to write a non-null typed array into the stream. /// /// /// Checks first to see if the element type is a primitive type and calls the /// correct routine if so. Otherwise determines the best, optimized method /// to store the array contents. /// /// An array of object elements never stores its type. /// /// The non-null typed array to store. /// True if the type should be stored; false otherwise private void WriteTypedArray(Array value, bool storeType) { var elementType = value.GetType().GetElementType(); if (elementType == typeof(object)) { storeType = false; } if (elementType == typeof(string)) { WriteTypeCode(SerializedType.StringArrayType); WriteOptimized((object[]) value); } else if (elementType == typeof(Int32)) { WriteTypeCode(SerializedType.Int32ArrayType); if (OptimizeForSize) { WriteOptimized((Int32[]) value); } else { Write((Int32[]) value); } } else if (elementType == typeof(Int16)) { WriteTypeCode(SerializedType.Int16ArrayType); if (OptimizeForSize) { WriteOptimized((Int16[]) value); } else { Write((Int16[]) value); } } else if (elementType == typeof(Int64)) { WriteTypeCode(SerializedType.Int64ArrayType); if (OptimizeForSize) { WriteOptimized((Int64[]) value); } else { Write((Int64[]) value); } } else if (elementType == typeof(UInt32)) { WriteTypeCode(SerializedType.UInt32ArrayType); if (OptimizeForSize) { WriteOptimized((UInt32[]) value); } else { Write((UInt32[]) value); } } else if (elementType == typeof(UInt16)) { WriteTypeCode(SerializedType.UInt16ArrayType); if (OptimizeForSize) { WriteOptimized((UInt16[]) value); } else { Write((UInt16[]) value); } } else if (elementType == typeof(UInt64)) { WriteTypeCode(SerializedType.UInt64ArrayType); if (OptimizeForSize) { WriteOptimized((UInt64[]) value); } else { Write((UInt64[]) value); } } else if (elementType == typeof(Single)) { WriteTypeCode(SerializedType.SingleArrayType); WriteArray((Single[]) value); } else if (elementType == typeof(Double)) { WriteTypeCode(SerializedType.DoubleArrayType); WriteArray((Double[]) value); } else if (elementType == typeof(Decimal)) { WriteTypeCode(SerializedType.DecimalArrayType); WriteArray((Decimal[]) value); } else if (elementType == typeof(DateTime)) { WriteTypeCode(SerializedType.DateTimeArrayType); if (OptimizeForSize) { WriteOptimized((DateTime[]) value); } else { Write((DateTime[]) value); } } else if (elementType == typeof(TimeSpan)) { WriteTypeCode(SerializedType.TimeSpanArrayType); if (OptimizeForSize) { WriteOptimized((TimeSpan[]) value); } else { Write((TimeSpan[]) value); } } else if (elementType == typeof(Guid)) { WriteTypeCode(SerializedType.GuidArrayType); WriteArray((Guid[]) value); } else if (elementType == typeof(SByte)) { WriteTypeCode(SerializedType.SByteArrayType); WriteArray((SByte[]) value); } else if (elementType == typeof(Boolean)) { WriteTypeCode(SerializedType.BooleanArrayType); WriteArray((bool[]) value); } else if (elementType == typeof(Byte)) { WriteTypeCode(SerializedType.ByteArrayType); WriteArray((Byte[]) value); } else if (elementType == typeof(Char)) { WriteTypeCode(SerializedType.CharArrayType); WriteArray((Char[]) value); } else if (value.Length == 0) { WriteTypeCode(elementType == typeof(object) ? SerializedType.EmptyObjectArrayType : SerializedType.EmptyTypedArrayType); if (storeType) { WriteOptimized(elementType); } } else if (elementType == typeof(object)) { WriteTypeCode(SerializedType.ObjectArrayType); WriteObjectArray((object[]) value); } else { var optimizeFlags = IsTypeRecreatable(elementType) ? FullyOptimizableTypedArray : null; if (!elementType.IsValueType) { if ((optimizeFlags == null) || !ArrayElementsAreSameType((object[]) value, elementType)) { if (!storeType) { WriteTypeCode(SerializedType.ObjectArrayType); } else { WriteTypeCode(SerializedType.OtherTypedArrayType); WriteOptimized(elementType); } WriteObjectArray((object[]) value); return; } for (var i = 0; i < value.Length; i++) { if (value.GetValue(i) == null) { if (optimizeFlags == FullyOptimizableTypedArray) { optimizeFlags = new BitArray(value.Length); } optimizeFlags[i] = true; } } } WriteTypedArrayTypeCode(optimizeFlags, value.Length); if (storeType) { WriteOptimized(elementType); } for (var i = 0; i < value.Length; i++) { if (optimizeFlags == null) { WriteObject(value.GetValue(i)); } else if (optimizeFlags == FullyOptimizableTypedArray || !optimizeFlags[i]) { Write((IOwnedDataSerializable) value.GetValue(i), null); } } } } /// /// Checks whether instances of a Type can be created. /// /// /// A Value Type only needs to implement IOwnedDataSerializable. /// A Reference Type needs to implement IOwnedDataSerializableAndRecreatable and provide a default constructor. /// /// The Type to check /// true if the Type is recreatable; false otherwise. private static bool IsTypeRecreatable(Type type) { if (type.IsValueType) { return typeof(IOwnedDataSerializable).IsAssignableFrom(type); } return typeof(IOwnedDataSerializableAndRecreatable).IsAssignableFrom(type) && HasEmptyConstructor(type); } /// /// Checks whether a type has a default/empty constructor. /// /// The Type to check /// true if the Type has a default/empty constructor; false otherwise. private static bool HasEmptyConstructor(Type type) { return type.GetConstructor(Type.EmptyTypes) != null; } /// /// Checks whether each element in an array is of the same type. /// /// The array to check /// The expected element type. /// private static bool ArrayElementsAreSameType(object[] values, Type elementType) { foreach (var value in values) { if ((value != null) && (value.GetType() != elementType)) { return false; } } return true; } /// /// Writes the TypeCode for the Typed Array followed by the number of elements. /// /// /// private void WriteTypedArrayTypeCode(BitArray optimizeFlags, int length) { if (optimizeFlags == null) { WriteTypeCode(SerializedType.NonOptimizedTypedArrayType); } else if (optimizeFlags == FullyOptimizableTypedArray) { WriteTypeCode(SerializedType.FullyOptimizedTypedArrayType); } else { WriteTypeCode(SerializedType.PartiallyOptimizedTypedArrayType); WriteOptimized(optimizeFlags); } Write7BitEncodedSigned32BitValue(length); } /// /// gets the list of optional IFastSerializationTypeSurrogate instances which /// SerializationWriter and SerializationReader will use to serialize objects not directly supported. /// It is important to use the same list on both client and server ends to ensure that the same surrogated-types are supported. /// public static List TypeSurrogates { get { return typeSurrogates; } } /// /// Returns the number of strings in the string token table. /// public int StringTokenTableSize { get { return stringLookup.Count; } } internal int GetTotalStringSize() { var strings = (string[]) typeof(UniqueStringList).GetField("stringList", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(stringLookup); var result = 0; for (var i = 0; i < stringLookup.Count; i++) { result += strings[i].Length; } return result; } /// /// Returns the number of objects in the object token table. /// public int ObjectTokenTableSize { get { return objectTokenLookup.Count; } } /// /// Gets or Sets a boolean flag to indicate whether to optimize for size (default) /// by storing data as packed bits or sections where possible. /// Setting this value to false will turn off this optimization and store /// data directly which increases the speed. /// Note: This only affects optimization of data passed to the WriteObject method /// and direct calls to the WriteOptimized methods will always pack data into /// the smallest space where possible. /// public bool OptimizeForSize { get; set; } /// /// Gets or Sets a boolean flag to indicate whether to preserve the scale within /// a Decimal value when it would have no effect on the represented value. /// Note: a 2m value and a 2.00m value represent the same value but internally they /// are stored differently - the former has a value of 2 and a scale of 0 and /// the latter has a value of 200 and a scale of 2. /// The scaling factor also preserves any trailing zeroes in a Decimal number. /// Trailing zeroes do not affect the value of a Decimal number in arithmetic or /// comparison operations. However, trailing zeroes can be revealed by the ToString /// method if an appropriate format string is applied. /// From a serialization point of view, the former will take 2 bytes whereas the /// latter would take 4 bytes, therefore it is preferable to not save the scale where /// it doesn't affect the represented value. /// public bool PreserveDecimalScale { get; set; } #region Private Classes /// /// Private class used to wrap an object that is to be tokenized, and recreated at deserialization by its type. /// private class SingletonTypeWrapper { private readonly Type wrappedType; /// /// Initializes a new instance of the class. /// /// The value. public SingletonTypeWrapper(object value) { wrappedType = value.GetType(); } /// /// Gets the type of the wrapped. /// /// The type of the wrapped. public Type WrappedType { get { return wrappedType; } } /// /// Determines whether the specified is equal to the current . /// /// The to compare with the current . /// /// true if the specified is equal to the current ; otherwise, false. /// public override bool Equals(object obj) { return wrappedType.Equals(((SingletonTypeWrapper) obj).wrappedType); } /// /// Serves as a hash function for a particular type. /// /// /// A hash code for the current . /// public override int GetHashCode() { return wrappedType.GetHashCode(); } } /// /// Provides a faster way to store string tokens both maintaining the order that they were added and /// providing a fast lookup. /// /// Based on code developed by ewbi at http://ewbi.blogs.com/develops/2006/10/uniquestringlis.html /// private sealed class UniqueStringList { private const float LoadFactor = .72f; // Based on Golden Primes (as far as possible from nearest two powers of two) // at http://planetmath.org/encyclopedia/GoodHashTablePrimes.html private static readonly int[] PrimeNumberList = new[] { // 193, 769, 3079, 12289, 49157 removed to allow quadrupling of bucket table size // for smaller size then reverting to doubling 389, 1543, 6151, 24593, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741 }; private string[] stringList; private int[] buckets; private int bucketListCapacity; private int loadLimit; private int primeNumberListIndex; /// /// Initializes a new instance of the class. /// public UniqueStringList() { bucketListCapacity = PrimeNumberList[primeNumberListIndex++]; stringList = new string[bucketListCapacity]; buckets = new int[bucketListCapacity]; loadLimit = (int) (bucketListCapacity*LoadFactor); } /// /// Gets the count. /// /// The count. public int Count { get; private set; } public bool Add(string value, out int index) { var bucketIndex = GetBucketIndex(value); index = buckets[bucketIndex] - 1; if (index == -1) { stringList[Count++] = value; buckets[bucketIndex] = Count; if (Count > loadLimit) { Expand(); } index = Count - 1; return true; } return false; } /// /// Expands this instance. /// private void Expand() { bucketListCapacity = PrimeNumberList[primeNumberListIndex++]; buckets = new int[bucketListCapacity]; var newStringlist = new string[bucketListCapacity]; stringList.CopyTo(newStringlist, 0); stringList = newStringlist; Reindex(); } /// /// Reindexes this instance. /// private void Reindex() { loadLimit = (int) (bucketListCapacity*LoadFactor); for (var stringIndex = 0; stringIndex < Count; stringIndex++) { var index = GetBucketIndex(stringList[stringIndex]); buckets[index] = stringIndex + 1; } } /// /// Gets the index of the bucket. /// /// The value. /// private int GetBucketIndex(string value) { var hashCode = value.GetHashCode() & 0x7fffffff; var bucketIndex = hashCode%bucketListCapacity; var increment = (bucketIndex > 1) ? bucketIndex : 1; var i = bucketListCapacity; while (0 < i--) { var stringIndex = buckets[bucketIndex]; if (stringIndex == 0) { return bucketIndex; } if (value.Equals(stringList[stringIndex - 1])) { return bucketIndex; } bucketIndex = (bucketIndex + increment)%bucketListCapacity; // Probe. } throw new InvalidOperationException("Failed to locate a bucket."); } } #endregion } }