Click here to Skip to main content
15,896,269 members
Articles / Programming Languages / XML

Optimizing Serialization in .NET

Rate me:
Please Sign up or sign in to vote.
4.87/5 (88 votes)
16 May 2010Public Domain31 min read 648.9K   5K   332  
Provides code and techniques to enable developers to optimize serialization of data
#define THROW_IF_NOT_OPTIMIZABLE

using System;
using System.Collections;
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;
using System.Collections.Generic;

// ReSharper disable PossibleInvalidCastException
// ReSharper disable PossibleNullReferenceException
// ReSharper disable AssignNullToNotNullAttribute
// ReSharper disable ConditionIsAlwaysTrueOrFalse
// ReSharper disable HeuristicUnreachableCode

namespace Framework.Serialization
{
	/// <summary>
	/// 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.
	/// <br/><br/>
	/// 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.
	/// <para/>
	/// 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:
	/// <para/>
	/// <example><code>
	/// writer.UpdateHeader();
	/// MemoryStream stream = (MemoryStream) writer.BaseStream;
	///	serializedData = MyCompressor.Compress(stream.GetBuffer(), (int) stream.Length);
	/// </code></example>
	/// </summary>
	public sealed class SerializationWriter: BinaryWriter
	{
		#region Statics and constants
		/// <summary>
		/// Default capacity for the underlying MemoryStream
		/// </summary>
		public static int DefaultCapacity = 1024;

		/// <summary>
		/// The Default setting for the OptimizeForSize property.
		/// </summary>
		public static bool DefaultOptimizeForSize = true;

		/// <summary>
		/// The Default setting for the PreserveDecimalScale property.
		/// </summary>
		public static bool DefaultPreserveDecimalScale;

		/// <summary>
		/// 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.
		/// </summary>
		static readonly List<IFastSerializationTypeSurrogate> typeSurrogates = new List<IFastSerializationTypeSurrogate>();

		/// <summary>
		/// Section masks used for packing DateTime values
		/// </summary>
		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

		/// <summary>
		/// Section masks used for packing TimeSpan values
		/// </summary>
		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

		/// <summary>
		/// Holds the highest Int16 that can be optimized into less than the normal 2 bytes
		/// </summary>
		public const short HighestOptimizable16BitValue = 127; // 0x7F

		/// <summary>
		/// Holds the highest Int32 that can be optimized into less than the normal 4 bytes
		/// </summary>
		public const int HighestOptimizable32BitValue = 2097151; // 0x001FFFFF

		/// <summary>
		/// Holds the highest Int64 that can be optimized into less than the normal 8 bytes
		/// </summary>
		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
		static readonly BitArray FullyOptimizableTypedArray = new BitArray(0);
		#endregion

		readonly UniqueStringList stringLookup;
		readonly Hashtable objectTokenLookup;
		readonly bool allowUpdateHeader;
		readonly int startPosition;
		bool optimizeForSize;
		bool preserveDecimalScale;

		#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

		/// <summary>
		/// Dumps the type usage.
		/// </summary>
		[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

		/// <summary>
		/// Creates a FastSerializer with the Default Capacity (1kb)
		/// </summary>
		public SerializationWriter(): this(new MemoryStream(DefaultCapacity)) 
		{
		}

		/// <summary>
		/// Creates a FastSerializer with the specified capacity
		/// </summary>
		/// <param name="capacity"></param>
		public SerializationWriter(int capacity): this(new MemoryStream(capacity)) 
		{
		}

		/// <summary>
		/// Creates a FastSerializer around the specified stream
		/// Will allow updating of header info if the stream is seekable
		/// </summary>
		/// <param name="stream"></param>
		public SerializationWriter(Stream stream): this(stream, true)
		{
		}

		/// <summary>
		/// Creates a FastSerializer around the specified stream
		/// Notes:
		/// If the stream is not seekable then the allowUpdateHeader parameter is ignored
		/// </summary>
		/// <param name="stream">The stream in which to store data</param>
		/// <param name="allowUpdateHeader">true if token table presize 
		/// information can be stored; false otherwise</param>
		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;
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The ArrayList to store.</param>
		public void Write(ArrayList value)
		{
			if (value == null)
			{
				WriteTypeCode(SerializedType.NullType);
			}
			else
			{
				WriteTypeCode(SerializedType.ArrayListType);
				WriteOptimized(value);
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The BitArray value to store.</param>
		public void Write(BitArray value)
		{
			if (value == null)
			{
				WriteTypeCode(SerializedType.NullType);
			}
			else
			{
				WriteTypeCode(SerializedType.BitArrayType);
				WriteOptimized(value);
			}
		}

		/// <summary>
		/// Writes a BitVector32 into the stream.
		/// Stored Size: 4 bytes.
		/// </summary>
		/// <param name="value">The BitVector32 to store.</param>
		public void Write(BitVector32 value)
		{
			Write(value.Data);
		}

		/// <summary>
		/// Writes a DateTime value into the stream.
		/// Stored Size: 8 bytes
		/// </summary>
		/// <param name="value">The DateTime value to store.</param>
		public void Write(DateTime value)
		{
			Write(value.ToBinary());
		}

		/// <summary>
		/// Writes a Guid into the stream.
		/// Stored Size: 16 bytes.
		/// </summary>
		/// <param name="value"></param>
		public void Write(Guid value)
		{
			base.Write(value.ToByteArray());
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="target">The IOwnedDataSerializable object to ask for owned data</param>
		/// <param name="context">An arbtritrary object but BitVector32 recommended</param>
		public void Write(IOwnedDataSerializable target, object context)
		{
			target.SerializeOwnedData(this, context);
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The object to store.</param>
		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);
				}
			}
		}

		/// <summary>
		/// Calls WriteOptimized(string).
		/// This override to hide base BinaryWriter.Write(string).
		/// </summary>
		/// <param name="value">The string to store.</param>
		public override void Write(string value)
		{
			WriteOptimized(value);
		}

		/// <summary>
		/// Writes a TimeSpan value into the stream.
		/// Stored Size: 8 bytes
		/// </summary>
		/// <param name="value">The TimeSpan value to store.</param>
		public void Write(TimeSpan value)
		{
			Write(value.Ticks);
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The Type to store.</param>
		/// <param name="fullyQualified">true to store the AssemblyQualifiedName or false to store the FullName. </param>
		public void Write(Type value, bool fullyQualified)
		{
			if (value == null)
			{
				WriteTypeCode(SerializedType.NullType);
			}
			else
			{
				WriteTypeCode(SerializedType.TypeType);
				WriteOptimized(fullyQualified ? value.AssemblyQualifiedName : value.FullName);
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The ArrayList to store. Must not be null.</param>
		public void WriteOptimized(ArrayList value)
		{
			CheckOptimizable(value != null, "Cannot optimize a null ArrayList");

			WriteObjectArray(value.ToArray());
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The BitArray value to store. Must not be null.</param>
		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);
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The BitVector32 to store. Must not use more than 28 bits.</param>
		public void WriteOptimized(BitVector32 value)
		{
			CheckOptimizable(value.Data < OptimizationFailure32BitValue && value.Data >= 0, "BitVector32 value is not optimizable");
			Write7BitEncodedSigned32BitValue(value.Data);
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The DateTime value to store. Must not contain sub-millisecond data.</param>
		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);
			}
		}

		/// <summary>
		/// 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
		/// </summary>
		/// <param name="value">The Decimal value to store</param>
		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]);
				}
			}
		}

		/// <summary>
		/// Write an Int16 value using the fewest number of bytes possible.
		/// </summary>
		/// <remarks>
		/// 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)
		/// </remarks>
		/// <param name="value">The Int16 to store. Must be between 0 and 16,383 inclusive.</param>
		public void WriteOptimized(short value) 
		{
			CheckOptimizable(value < OptimizationFailure16BitValue && value >= 0, "Int16 value is not optimizable");

			Write7BitEncodedSigned32BitValue(value);
		}

		/// <summary>
		/// Write an Int32 value using the fewest number of bytes possible.
		/// </summary>
		/// <remarks>
		/// 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)
		/// </remarks>
		/// <param name="value">The Int32 to store. Must be between 0 and 268,435,455 inclusive.</param>
		public void WriteOptimized(int value)
		{
			CheckOptimizable(value < OptimizationFailure32BitValue && value >= 0, "Int32 value is not optimizable");

			Write7BitEncodedSigned32BitValue(value);
		}

		/// <summary>
		/// Write an Int64 value using the fewest number of bytes possible.
		/// </summary>
		/// <remarks>
		/// 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)
		/// </remarks>
		/// <param name="value">The Int64 to store. Must be between 0 and 72,057,594,037,927,935 inclusive.</param>
		public void WriteOptimized(long value)
		{
			CheckOptimizable(value < OptimizationFailure64BitValue && value >= 0, "long value is not optimizable");

			Write7BitEncodedSigned64BitValue(value);
		}

		/// <summary>
		/// 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!!!)
		/// </summary>
		/// <param name="value">The string to store.</param>
		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
				}
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The TimeSpan value to store. Must not contain sub-millisecond data.</param>
		public void WriteOptimized(TimeSpan value)
		{
			CheckOptimizable(((value.Ticks % TimeSpan.TicksPerMillisecond) == 0), "Cannot optimize a TimeSpan with sub-millisecond accuracy");

			EncodeTimeSpan(value, false, 0);
		}
		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The Type to store. Must not be null.</param>
		public void WriteOptimized(Type value)
		{
			CheckOptimizable((value != null), "Cannot optimize a null Type");

			WriteOptimized(value.AssemblyQualifiedName.IndexOf(", mscorlib,") == -1 ? value.AssemblyQualifiedName : value.FullName);
		}

		/// <summary>
		/// Write a UInt16 value using the fewest number of bytes possible.
		/// </summary>
		/// <remarks>
		/// 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)
		/// </remarks>
		/// <param name="value">The UInt16 to store. Must be between 0 and 16,383 inclusive.</param>
		[CLSCompliant(false)]
		public void WriteOptimized(ushort value)
		{
			CheckOptimizable(value < OptimizationFailure16BitValue, "UInt16 value is not optimizable");

			Write7BitEncodedUnsigned32BitValue(value);
		}

		/// <summary>
		/// Write a UInt32 value using the fewest number of bytes possible.
		/// </summary>
		/// <remarks>
		/// 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)
		/// </remarks>
		/// <param name="value">The UInt32 to store. Must be between 0 and 268,435,455 inclusive.</param>
		[CLSCompliant(false)]
		public void WriteOptimized(uint value)
		{
			CheckOptimizable(value < OptimizationFailure32BitValue, "UInt32 value is not optimizable");

			Write7BitEncodedUnsigned32BitValue(value);
		}

		/// <summary>
		/// Write a UInt64 value using the fewest number of bytes possible.
		/// </summary>
		/// <remarks>
		/// 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)
		/// </remarks>
		/// <param name="value">The UInt64 to store. Must be between 0 and 72,057,594,037,927,935 inclusive.</param>
		[CLSCompliant(false)]
		public void WriteOptimized(ulong value)
		{
			CheckOptimizable(value < OptimizationFailure64BitValue, "ulong value is not optimizable");

			Write7BitEncodedUnsigned64BitValue(value);
		}

		/// <summary>
		/// Writes a Boolean[] into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// Calls WriteOptimized(Boolean[]).
		/// </summary>
		/// <param name="values">The Boolean[] to store.</param>
		public void Write(bool[] values)
		{
			WriteOptimized(values);
		}

		/// <summary>
		/// Writes a Byte[] into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The Byte[] to store.</param>
		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);
			}
		}

		/// <summary>
		/// Writes a Char[] into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The Char[] to store.</param>
		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);
			}
		}

		/// <summary>
		/// Writes a DateTime[] into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The DateTime[] to store.</param>
		public void Write(DateTime[] values)
		{
			if (values == null)
			{
				WriteTypeCode(SerializedType.NullType);
			}
			else if (values.Length == 0)
			{
				WriteTypeCode(SerializedType.EmptyTypedArrayType);
			}
			else
			{
				WriteArray(values, null);
			}
		}

		/// <summary>
		/// Writes a Decimal[] into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// Calls WriteOptimized(Decimal[]).
		/// </summary>
		/// <param name="values">The Decimal[] to store.</param>
		public void Write(decimal[] values)
		{
			WriteOptimized(values);
		}

		/// <summary>
		/// Writes a Double[] into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The Double[] to store.</param>
		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);
			}
		}

		/// <summary>
		/// Writes a Single[] into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The Single[] to store.</param>
		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);
			}
		}

		/// <summary>
		/// Writes a Guid[] into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The Guid[] to store.</param>
		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);
			}
		}
		/// <summary>
		/// Writes an Int32[] into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The Int32[] to store.</param>
		public void Write(int[] values)
		{
			if (values == null)
			{
				WriteTypeCode(SerializedType.NullType);
			}
			else if (values.Length == 0)
			{
				WriteTypeCode(SerializedType.EmptyTypedArrayType);
			}
			else
			{
				WriteArray(values, null);
			}
		}

		/// <summary>
		/// Writes an Int64[] into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The Int64[] to store.</param>
		public void Write(long[] values)
		{
			if (values == null)
			{
				WriteTypeCode(SerializedType.NullType);
			}
			else if (values.Length == 0)
			{
				WriteTypeCode(SerializedType.EmptyTypedArrayType);
			}
			else
			{
				WriteArray(values, null);
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="values">The object[] to store.</param>
		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);
			}
		}

		/// <summary>
		/// Writes an SByte[] into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The SByte[] to store.</param>
		[CLSCompliant(false)]
		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);
			}
		}
		/// <summary>
		/// Writes an Int16[]or a null into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// Calls WriteOptimized(decimal[]).
		/// </summary>
		/// <param name="values">The Int16[] to store.</param>
		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);
			}
		}

		/// <summary>
		/// Writes a TimeSpan[] into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The TimeSpan[] to store.</param>
		public void Write(TimeSpan[] values)
		{
			if (values == null)
			{
				WriteTypeCode(SerializedType.NullType);
			}
			else if (values.Length == 0)
			{
				WriteTypeCode(SerializedType.EmptyTypedArrayType);
			}
			else
			{
				WriteArray(values, null);
			}
		}

		/// <summary>
		/// Writes a UInt32[] into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The UInt32[] to store.</param>
		[CLSCompliant(false)]
		public void Write(uint[] values)
		{
			if (values == null)
			{
				WriteTypeCode(SerializedType.NullType);
			}
			else if (values.Length == 0)
			{
				WriteTypeCode(SerializedType.EmptyTypedArrayType);
			}
			else
			{
				WriteArray(values, null);
			}
		}
		/// <summary>
		/// Writes a UInt64[] into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The UInt64[] to store.</param>
		[CLSCompliant(false)]
		public void Write(ulong[] values)
		{
			if (values == null)
			{
				WriteTypeCode(SerializedType.NullType);
			}
			else if (values.Length == 0)
			{
				WriteTypeCode(SerializedType.EmptyTypedArrayType);
			}
			else
			{
				WriteArray(values, null);
			}
		}

		/// <summary>
		/// Writes a UInt16[] into the stream.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The UInt16[] to store.</param>
		[CLSCompliant(false)]
		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);
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="values">The Boolean[] to store.</param>
		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);
			}
		}

		/// <summary>
		/// Writes a DateTime[] into the stream using the fewest possible bytes.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The DateTime[] to store.</param>
		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);
			}
		}

		/// <summary>
		/// Writes a Decimal[] into the stream using the fewest possible bytes.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The Decimal[] to store.</param>
		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);
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="values">The object[] to store. Must not be null.</param>
		public void WriteOptimized(object[] values)
		{
			CheckOptimizable(values != null, "Cannot optimize a null object[]");

			WriteObjectArray(values);
		}

		
		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="values1">The first object[] value which must not be null and must have the same length as values2</param>
		/// <param name="values2">The second object[] value which must not be null and must have the same length as values1</param>
		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);
					}
				}
			}
		}

		/// <summary>
		/// Writes an Int16[] into the stream using the fewest possible bytes.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The Int16[] to store.</param>
		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);
			}
		}
		/// <summary>
		/// Writes an Int32[] into the stream using the fewest possible bytes.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The Int32[] to store.</param>
		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);
			}
		}

		/// <summary>
		/// Writes an Int64[] into the stream using the fewest possible bytes.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The Int64[] to store.</param>
		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);
			}
		}

		/// <summary>
		/// Writes a TimeSpan[] into the stream using the fewest possible bytes.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The TimeSpan[] to store.</param>
		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);
			}
		}

		/// <summary>
		/// Writes a UInt16[] into the stream using the fewest possible bytes.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The UInt16[] to store.</param>
		[CLSCompliant(false)]
		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);
			}
		}

		/// <summary>
		/// Writes a UInt32[] into the stream using the fewest possible bytes.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The UInt32[] to store.</param>
		[CLSCompliant(false)]
		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);
			}
		}

		/// <summary>
		/// Writes a UInt64[] into the stream using the fewest possible bytes.
		/// Notes:
		/// A null or empty array will take 1 byte.
		/// </summary>
		/// <param name="values">The UInt64[] to store.</param>
		[CLSCompliant(false)]
		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);
			}
		}

		
		/// <summary>
		/// Writes a Nullable type into the stream.
		/// Synonym for WriteObject().
		/// </summary>
		/// <param name="value">The Nullable value to store.</param>
		public void WriteNullable(ValueType value)
		{
			WriteObject(value);
		}
		
		/// <summary>
		/// Writes a non-null generic Dictionary into the stream.
		/// </summary>
		/// <remarks>
		/// The key and value types themselves are not stored - they must be 
		/// supplied at deserialization time.
		/// <para/>
		/// An array of keys is stored followed by an array of values.
		/// </remarks>
		/// <typeparam name="K">The key Type.</typeparam>
		/// <typeparam name="V">The value Type.</typeparam>
		/// <param name="value">The generic dictionary.</param>
		public void Write<K, V>(Dictionary<K, V> 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);
		}
		

		/// <summary>
		/// Writes a non-null generic List into the stream.
		/// </summary>
		/// <remarks>
		/// The list type itself is not stored - it must be supplied
		/// at deserialization time.
		/// <para/>
		/// The list contents are stored as an array.
		/// </remarks>
		/// <typeparam name="T">The list Type.</typeparam>
		/// <param name="value">The generic List.</param>
		public void Write<T>(List<T> value)
		{
			WriteTypedArray(value.ToArray(), false);
		}

		/// <summary>
		/// Writes a null or a typed array into the stream.
		/// </summary>
		/// <param name="values">The array to store.</param>
		public void WriteTypedArray(Array values)
		{
			if (values == null)
			{
				WriteTypeCode(SerializedType.NullType);
			}
			else
			{
				WriteTypedArray(values, true);
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <returns>The total length of serialized data or 0 if the stream is not seekable</returns>
		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;
		}

		/// <summary>
		/// 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.
		/// 
		/// </summary>
		/// <returns>A byte[] containing all serialized data.</returns>
		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;
		}

		/// <summary>
		/// 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
		/// </summary>
		/// <param name="value">The byte[] to store. Must not be null.</param>
		public void WriteBytesDirect(byte[] value)
		{
			base.Write(value);
		}

		/// <summary>
		/// Writes a non-null string directly to the stream without tokenization.
		/// </summary>
		/// <param name="value">The string to store. Must not be null.</param>
		public void WriteStringDirect(string value)
		{
			CheckOptimizable(value != null, "Cannot directly write a null string");

			base.Write(value);
		}

		/// <summary>
		/// 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.
		/// 
		/// </summary>
		/// <param name="value">The object to tokenize. Must not be null and must not be a string.</param>
		public void WriteTokenizedObject(object value)
		{
			WriteTokenizedObject(value, false);
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The object to tokenize. Must not be null and must not be a string.</param>
		/// <param name="recreateFromType">true if the object can be recreated using a parameterless constructor; 
		/// false if the object should be serialized as-is</param>
		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
			}
		}
		

		/// <summary>
		/// Finds the surrogate type for the type passed in.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <returns></returns>
		internal static IFastSerializationTypeSurrogate FindSurrogateForType(Type type) 
		{
			foreach(var surrogate in TypeSurrogates) 
			{
				if (surrogate.SupportsType(type)) return surrogate;
			}

			return null;
		}

		/// <summary>
		/// Creates the binary formatter.
		/// </summary>
		/// <returns></returns>
		static BinaryFormatter CreateBinaryFormatter()
		{
			return new BinaryFormatter
			       	{
			       		AssemblyFormat = FormatterAssemblyStyle.Full
			       	};
		}
		

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The TimeSpan to store.</param>
		/// <param name="partOfDateTime">True if the TimeSpan is the TimeOfDay from a DateTime; False if a real TimeSpan.</param>
		/// <param name="initialData">The intial data for the BitVector32 - contains DateTimeKind or 0</param>
		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);
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="condition">An expression evaluating to true if the optimization condition is met, false otherwise.</param>
		/// <param name="message">The message to include in the exception should the optimization condition not be met.</param>
		[Conditional("THROW_IF_NOT_OPTIMIZABLE")]
		static void CheckOptimizable(bool condition, string message)
		{
			if (!condition)
			{
				throw new InvalidOperationException(message);
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The Int32 value to encode.</param>
		void Write7BitEncodedSigned32BitValue(int value)
		{
			var unsignedValue = unchecked((uint) value);

			while(unsignedValue >= 0x80)
			{
				Write((byte) (unsignedValue | 0x80));
				unsignedValue >>= 7;
			}

			Write((byte) unsignedValue);
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The Int64 value to encode.</param>
		void Write7BitEncodedSigned64BitValue(long value)
		{
			var unsignedValue = unchecked((ulong) value);

			while(unsignedValue >= 0x80)
			{
				Write((byte) (unsignedValue | 0x80));
				unsignedValue >>= 7;
			}

			Write((byte) unsignedValue);
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The UInt32 value to encode.</param>
		void Write7BitEncodedUnsigned32BitValue(uint value)
		{
			while(value >= 0x80)
			{
				Write((byte) (value | 0x80));
				value >>= 7;
			}

			Write((byte) value);
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="value">The ULong value to encode.</param>
		void Write7BitEncodedUnsigned64BitValue(ulong value)
		{
			while(value >= 0x80)
			{
				Write((byte) (value | 0x80));
				value >>= 7;
			}

			Write((byte) value);
		}

		/// <summary>
		/// Internal implementation to store a non-null Boolean[].
		/// </summary>
		/// <remarks>
		/// Stored as a BitArray for optimization.
		/// </remarks>
		/// <param name="values">The Boolean[] to store.</param>
		void WriteArray(bool[] values) 
		{
			WriteOptimized(new BitArray(values));
		}

		/// <summary>
		/// Internal implementation to store a non-null Byte[].
		/// </summary>
		/// <param name="values">The Byte[] to store.</param>
		void WriteArray(byte[] values)
		{
			Write7BitEncodedSigned32BitValue(values.Length);

			if (values.Length > 0)
			{
				base.Write(values);
			}
		}

		/// <summary>
		/// Internal implementation to store a non-null Char[].
		/// </summary>
		/// <param name="values">The Char[] to store.</param>
		void WriteArray(char[] values)
		{
			Write7BitEncodedSigned32BitValue(values.Length);

			if (values.Length > 0)
			{
				base.Write(values);
			}
		}

		/// <summary>
		/// Internal implementation to write a non, null DateTime[] using a BitArray to 
		/// determine which elements are optimizable.
		/// </summary>
		/// <param name="values">The DateTime[] to store.</param>
		/// <param name="optimizeFlags">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.</param>
		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]);
				}
			}
		}

		/// <summary>
		/// Internal implementation to store a non-null Decimal[].
		/// </summary>
		/// <remarks>
		/// All elements are stored optimized.
		/// </remarks>
		/// <param name="values">The Decimal[] to store.</param>
		void WriteArray(decimal[] values)
		{
			Write7BitEncodedSigned32BitValue(values.Length);

			for(var i = 0; i < values.Length; i++)
			{
				WriteOptimized(values[i]);
			}
		}

		/// <summary>
		/// Internal implementation to store a non-null Double[].
		/// </summary>
		/// <param name="values">The Double[] to store.</param>
		void WriteArray(double[] values)
		{
			Write7BitEncodedSigned32BitValue(values.Length);

			foreach(var value in values)
			{
				Write(value);
			}
		}

		/// <summary>
		/// Internal implementation to store a non-null Single[].
		/// </summary>
		/// <param name="values">The Single[] to store.</param>
		void WriteArray(float[] values)
		{
			Write7BitEncodedSigned32BitValue(values.Length);

			foreach(var value in values)
			{
				Write(value);
			}
		}

		/// <summary>
		/// Internal implementation to store a non-null Guid[].
		/// </summary>
		/// <param name="values">The Guid[] to store.</param>
		void WriteArray(Guid[] values)
		{
			Write7BitEncodedSigned32BitValue(values.Length);

			foreach(var value in values)
			{
				Write(value);
			}
		}

		/// <summary>
		/// Internal implementation to write a non-null Int16[] using a BitArray to determine which elements are optimizable.
		/// </summary>
		/// <param name="values">The Int16[] to store.</param>
		/// <param name="optimizeFlags">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.</param>
		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]);
				}
			}
		}

		/// <summary>
		/// Internal implementation to write a non-null Int32[] using a BitArray to determine which elements are optimizable.
		/// </summary>
		/// <param name="values">The Int32[] to store.</param>
		/// <param name="optimizeFlags">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.</param>
		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]);
				}
			}
		}

		/// <summary>
		/// Internal implementation to writes a non-null Int64[] using a BitArray to determine which elements are optimizable.
		/// </summary>
		/// <param name="values">The Int64[] to store.</param>
		/// <param name="optimizeFlags">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.</param>
		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]);
				}
			}
		}

		/// <summary>
		/// Internal implementation to store a non-null SByte[].
		/// </summary>
		/// <param name="values">The SByte[] to store.</param>
		void WriteArray(sbyte[] values)
		{
			Write7BitEncodedSigned32BitValue(values.Length);

			foreach(var value in values)
			{
				Write(value);
			}
		}

		/// <summary>
		/// Internal implementation to store a non-null Int16[].
		/// </summary>
		/// <param name="values">The Int16[] to store.</param>
		void WriteArray(short[] values)
		{
			Write7BitEncodedSigned32BitValue(values.Length);

			foreach(var value in values)
			{
				Write(value);
			}
		}

		/// <summary>
		/// Internal implementation to write a non-null TimeSpan[] using a BitArray to determine which elements are optimizable.
		/// </summary>
		/// <param name="values">The TimeSpan[] to store.</param>
		/// <param name="optimizeFlags">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.</param>
		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]);
				}
			}
		}

		/// <summary>
		/// Internal implementation to write a non-null UInt16[] using a BitArray to determine which elements are optimizable.
		/// </summary>
		/// <param name="values">The UInt16[] to store.</param>
		/// <param name="optimizeFlags">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.</param>
		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]);
				}
			}
		}

		/// <summary>
		/// Internal implementation to write a non-null UInt32[] using a BitArray to determine which elements are optimizable.
		/// </summary>
		/// <param name="values">The UInt32[] to store.</param>
		/// <param name="optimizeFlags">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.</param>
		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]);
				}
			}
		}

		/// <summary>
		/// Internal implementation to store a non-null UInt16[].
		/// </summary>
		/// <param name="values">The UIn16[] to store.</param>
		void WriteArray(ushort[] values)
		{
			Write7BitEncodedSigned32BitValue(values.Length);

			foreach(var value in values)
			{
				Write(value);
			}
		}

		/// <summary>
		/// Internal implementation to write a non-null UInt64[] using a BitArray to determine which elements are optimizable.
		/// </summary>
		/// <param name="values">The UInt64[] to store.</param>
		/// <param name="optimizeFlags">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.</param>
		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]);
				}
			}
		}

		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="values"></param>
		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);
				}
			}
		}

		
		/// <summary>
		/// 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.
		/// </summary>
		/// <param name="typeCode">The SerializedType to store.</param>
		void WriteTypeCode(SerializedType typeCode)
		{
			Write((byte) typeCode);
#if DEBUG
			typeUsage[(int) typeCode]++;
#endif
		}

		/// <summary>
		/// Internal implementation to write a non-null typed array into the stream.
		/// </summary>
		/// <remarks>
		/// 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.
		/// <para/>
		/// An array of object elements never stores its type.
		/// </remarks>
		/// <param name="value">The non-null typed array to store.</param>
		/// <param name="storeType">True if the type should be stored; false otherwise</param>
		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);
					}
				}
			}
		}

		/// <summary>
		/// Checks whether instances of a Type can be created.
		/// </summary>
		/// <remarks>
		/// A Value Type only needs to implement IOwnedDataSerializable. 
		/// A Reference Type needs to implement IOwnedDataSerializableAndRecreatable and provide a default constructor.
		/// </remarks>
		/// <param name="type">The Type to check</param>
		/// <returns>true if the Type is recreatable; false otherwise.</returns>
		static bool IsTypeRecreatable(Type type)
		{
			if (type.IsValueType) return typeof(IOwnedDataSerializable).IsAssignableFrom(type);

			return typeof(IOwnedDataSerializableAndRecreatable).IsAssignableFrom(type) && HasEmptyConstructor(type);
		}

		/// <summary>
		/// Checks whether a type has a default/empty constructor.
		/// </summary>
		/// <param name="type">The Type to check</param>
		/// <returns>true if the Type has a default/empty constructor; false otherwise.</returns>
		static bool HasEmptyConstructor(Type type)
		{
			return type.GetConstructor(Type.EmptyTypes) != null;
		}

		/// <summary>
		/// Checks whether each element in an array is of the same type.
		/// </summary>
		/// <param name="values">The array to check</param>
		/// <param name="elementType">The expected element type.</param>
		/// <returns></returns>
		static bool ArrayElementsAreSameType(object[] values, Type elementType)
		{
			foreach(var value in values)
			{
				if ((value != null) && (value.GetType() != elementType)) return false;
			}

			return true;
		}

		/// <summary>
		/// Writes the TypeCode for the Typed Array followed by the number of elements.
		/// </summary>
		/// <param name="optimizeFlags"></param>
		/// <param name="length"></param>
		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);
		}

		/// <summary>
		/// 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.
		/// </summary>
		public static List<IFastSerializationTypeSurrogate> TypeSurrogates 
		{
			get { return typeSurrogates; }			
		}

		/// <summary>
		/// Returns the number of strings in the string token table.
		/// </summary>
		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;
		}

		/// <summary>
		/// Returns the number of objects in the object token table.
		/// </summary>
		public int ObjectTokenTableSize
		{
			get { return objectTokenLookup.Count; }
		}

		/// <summary>
		/// 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.
		/// </summary>
		public bool OptimizeForSize
		{
			get { return optimizeForSize; }
			set { optimizeForSize = value; }
		}

		/// <summary>
		/// 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.
		/// </summary>
		public bool PreserveDecimalScale
		{
			get { return preserveDecimalScale; }
			set { preserveDecimalScale = value; }
		} 

		#region Private Classes
		/// <summary>
		/// Private class used to wrap an object that is to be tokenized, and recreated at deserialization by its type.
		/// </summary>
		class SingletonTypeWrapper
		{
			readonly Type wrappedType;

			/// <summary>
			/// Initializes a new instance of the <see cref="SingletonTypeWrapper"/> class.
			/// </summary>
			/// <param name="value">The value.</param>
			public SingletonTypeWrapper(object value)
			{
				wrappedType = value.GetType();
			}

			/// <summary>
			/// Determines whether the specified <see cref="T:System.Object"></see> is equal to the current <see cref="T:System.Object"></see>.
			/// </summary>
			/// <param name="obj">The <see cref="T:System.Object"></see> to compare with the current <see cref="T:System.Object"></see>.</param>
			/// <returns>
			/// true if the specified <see cref="T:System.Object"></see> is equal to the current <see cref="T:System.Object"></see>; otherwise, false.
			/// </returns>
			public override bool Equals(object obj)
			{
				return wrappedType.Equals(((SingletonTypeWrapper) obj).wrappedType);
			}

			/// <summary>
			/// Serves as a hash function for a particular type.
			/// </summary>
			/// <returns>
			/// A hash code for the current <see cref="T:System.Object"></see>.
			/// </returns>
			public override int GetHashCode()
			{
				return wrappedType.GetHashCode();
			}

			/// <summary>
			/// Gets the type of the wrapped.
			/// </summary>
			/// <value>The type of the wrapped.</value>
			public Type WrappedType
			{
				get { return wrappedType; }
			}
		}

		/// <summary>
		/// 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
		/// </summary>
		sealed class UniqueStringList
		{
			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
			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
				};

			string[] stringList;
			int[] buckets;
			int bucketListCapacity;
			int stringListIndex;
			int loadLimit;
			int primeNumberListIndex;

			/// <summary>
			/// Initializes a new instance of the <see cref="UniqueStringList"/> class.
			/// </summary>
			public UniqueStringList()
			{
				bucketListCapacity = PrimeNumberList[primeNumberListIndex++];
				stringList = new string[bucketListCapacity];
				buckets = new int[bucketListCapacity];
				loadLimit = (int)(bucketListCapacity * LoadFactor);
			}

			public bool Add(string value, out int index)
			{
				var bucketIndex = GetBucketIndex(value);
				index = buckets[bucketIndex] - 1;
				if (index == -1)
				{
					stringList[stringListIndex++] = value;
					buckets[bucketIndex] = stringListIndex;
					if (stringListIndex > loadLimit)
					{
						Expand();
					}
					index = stringListIndex - 1;
					return true;
				}
				return false;
			}

			/// <summary>
			/// Expands this instance.
			/// </summary>
			void Expand()
			{
				bucketListCapacity = PrimeNumberList[primeNumberListIndex++];
				buckets = new int[bucketListCapacity];
				var newStringlist = new string[bucketListCapacity];
				stringList.CopyTo(newStringlist, 0);
				stringList = newStringlist;
				Reindex();
			}

			/// <summary>
			/// Reindexes this instance.
			/// </summary>
			void Reindex()
			{
				loadLimit = (int)(bucketListCapacity * LoadFactor);
				for(var stringIndex = 0; stringIndex < stringListIndex; stringIndex++)
				{
					var index = GetBucketIndex(stringList[stringIndex]);
					buckets[index] = stringIndex + 1;
				}
			}

			/// <summary>
			/// Gets the index of the bucket.
			/// </summary>
			/// <param name="value">The value.</param>
			/// <returns></returns>
			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.");
			}

			/// <summary>
			/// Gets the count.
			/// </summary>
			/// <value>The count.</value>
			public int Count
			{
				get { return stringListIndex; }
			}
		}
		#endregion

	}
}

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

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

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Software Developer (Senior) Hunton Information Systems Ltd.
United Kingdom United Kingdom
Simon Hewitt is a freelance IT consultant and is MD of Hunton Information Systems Ltd.

He is currently looking for contract work in London.

He is happily married to Karen (originally from Florida, US), has a lovely daughter Bailey, and they live in Kings Langley, Hertfordshire, UK.

Comments and Discussions