private static object ReadSerialized(BinaryReader reader, bool isNull, out Type objectType)
        {
            var typeName = reader.ReadString();

            objectType = Type.GetType(typeName, true, true);

            if (isNull)
            {
                return(null);
            }

            var type = objectType;

            if (!SerializerUtil.IsInstantiable(objectType))
            {
                var realTypeName = reader.ReadString();
                type = Type.GetType(realTypeName, true, true);
            }

            var memberCount = reader.ReadInt32();
            var info        = new SerializationInfo(objectType);

            for (int i = 0; i < memberCount; i++)
            {
                ReadMember(reader, info);
            }

            return(SerializerUtil.CreateObject(type, info));
        }
        private static void WriteSerializable(BinaryWriter writer, Type objectType, object obj)
        {
            writer.Write(objectType.AssemblyQualifiedName);

            if (obj == null)
            {
                return;
            }

            if (!SerializerUtil.IsInstantiable(objectType))
            {
                writer.Write(obj.GetType().AssemblyQualifiedName);
            }

            var info = new SerializationInfo(objectType);

            ((ISerializable)obj).GetObjectData(info);

            writer.Write(info.MemberCount);

            foreach (var memberInfo in info)
            {
                WriteMember(writer, memberInfo);
            }
        }
        private static void WriteGenericTypeArgument(BinaryWriter writer, Type type)
        {
            var objTypeCode = SerializerUtil.GetObjectTypeCode(type, null);

            writer.Write((byte)objTypeCode);

            switch (objTypeCode)
            {
            case ObjectTypeCode.Serializable:
                writer.Write(type.AssemblyQualifiedName);
                break;

            case ObjectTypeCode.Primitive: {
                var primitiveCode = SerializerUtil.GetPrimitiveCode(type);
                writer.Write(SerializerUtil.IsNullable(type));
                writer.Write((byte)primitiveCode);
                break;
            }

            case ObjectTypeCode.Object:
                break;

            default:
                throw new SerializationException($"The type {type} is not a supported generic agument type");
            }
        }
        private static void WriteArray(BinaryWriter writer, Type objectType, object obj)
        {
            var elementType  = objectType.GetElementType();
            var elemTypeCode = SerializerUtil.GetObjectTypeCode(elementType, obj);

            writer.Write((byte)elemTypeCode);

            switch (elemTypeCode)
            {
            case ObjectTypeCode.Primitive:
                var primitiveTypeCode = SerializerUtil.GetPrimitiveCode(elementType);
                writer.Write((byte)primitiveTypeCode);
                break;

            case ObjectTypeCode.Serializable:
                var name = elementType.AssemblyQualifiedName;

                writer.Write(name);
                break;

            case ObjectTypeCode.Object:
                break;

            default:
                throw new NotSupportedException($"Array of type {elementType} is not supported");
            }

            if (obj == null)
            {
                return;
            }

            var array = (Array)obj;

            if (array.Rank > 1)
            {
                throw new NotSupportedException("Multi-dimensional arrays not supported");
            }

            writer.Write(array.Length);

            for (int i = 0; i < array.Length; i++)
            {
                var value = array.GetValue(i);
                WriteObject(writer, elementType, value);
            }
        }
        private static object ReadArray(BinaryReader reader, bool isNull, out Type objectType)
        {
            var  elemObjType = (ObjectTypeCode)reader.ReadByte();
            Type elementType;

            if (elemObjType == ObjectTypeCode.Primitive)
            {
                var primitiveTypeCode = (PrimitiveTypeCode)reader.ReadByte();
                elementType = SerializerUtil.GetObjectType(primitiveTypeCode);
            }
            else if (elemObjType == ObjectTypeCode.Serializable)
            {
                var typeName = reader.ReadString();
                elementType = Type.GetType(typeName, true, true);
            }
            else
            {
                throw new NotSupportedException();
            }

            objectType = elementType.MakeArrayType();

            if (isNull)
            {
                return(null);
            }

            var length = reader.ReadInt32();
            var array  = Array.CreateInstance(elementType, length);

            for (int i = 0; i < length; i++)
            {
                Type itemType;
                var  item = ReadObject(reader, out itemType);

                if (!elementType.GetTypeInfo().IsAssignableFrom(itemType.GetTypeInfo()))
                {
                    throw new SerializationException();
                }

                array.SetValue(item, i);
            }

            return(array);
        }
        private static void WriteObject(BinaryWriter writer, Type objectType, object obj)
        {
            var objTypeCode = SerializerUtil.GetObjectTypeCode(objectType, obj);

            writer.Write((byte)objTypeCode);

            writer.Write(obj == null ? (byte)0 : (byte)1);

            switch (objTypeCode)
            {
            case ObjectTypeCode.Serializable:
                WriteSerializable(writer, objectType, obj);
                break;

            case ObjectTypeCode.Object:
                WriteUnknownObject(writer, obj);
                break;

            case ObjectTypeCode.Primitive:
                WritePrimitive(writer, objectType, obj);
                break;

            case ObjectTypeCode.Enum:
                WriteEnum(writer, objectType, obj);
                break;

            case ObjectTypeCode.Array:
                WriteArray(writer, objectType, obj);
                break;

            case ObjectTypeCode.List:
                WriteList(writer, objectType, obj);
                break;

            case ObjectTypeCode.Dictionary:
                WriteDictionary(writer, objectType, obj);
                break;

            default:
                throw new NotSupportedException();
            }
        }
        private static Type ReadGenericArgumentType(BinaryReader reader)
        {
            var objTypeCode = (ObjectTypeCode)reader.ReadByte();

            switch (objTypeCode)
            {
            case ObjectTypeCode.Primitive: {
                var nullable          = reader.ReadBoolean();
                var primitiveTypeCode = (PrimitiveTypeCode)reader.ReadByte();
                var primitiveType     = SerializerUtil.GetObjectType(primitiveTypeCode);
                return(nullable ? typeof(Nullable <>).MakeGenericType(primitiveType) : primitiveType);
            }

            case ObjectTypeCode.Serializable:
                return(Type.GetType(reader.ReadString(), true, true));

            case ObjectTypeCode.Object:
                return(typeof(object));

            default:
                throw new NotSupportedException();
            }
        }
        private static object ReadPrimitive(BinaryReader reader, bool isNull, out Type objectType)
        {
            var nullable = reader.ReadBoolean();
            var typeCode = (PrimitiveTypeCode)reader.ReadByte();

            objectType = SerializerUtil.GetObjectType(typeCode);

            if (nullable)
            {
                objectType = typeof(Nullable <>).MakeGenericType(objectType);
            }

            if (isNull)
            {
                return(null);
            }

            switch (typeCode)
            {
            case PrimitiveTypeCode.Boolean:
                return(reader.ReadBoolean());

            case PrimitiveTypeCode.Byte:
                return(reader.ReadByte());

            case PrimitiveTypeCode.SByte:
                return(reader.ReadSByte());

            case PrimitiveTypeCode.Int16:
                return(reader.ReadInt16());

            case PrimitiveTypeCode.UInt16:
                return(reader.ReadUInt16());

            case PrimitiveTypeCode.Int32:
                return(reader.ReadInt32());

            case PrimitiveTypeCode.UInt32:
                return(reader.ReadUInt32());

            case PrimitiveTypeCode.Int64:
                return(reader.ReadInt64());

            case PrimitiveTypeCode.UInt64:
                return(reader.ReadUInt64());

            case PrimitiveTypeCode.Single:
                return(reader.ReadSingle());

            case PrimitiveTypeCode.Double:
                return(reader.ReadDouble());

            case PrimitiveTypeCode.Char:
                return(reader.ReadChar());

            case PrimitiveTypeCode.String:
                return(reader.ReadString());

            default:
                throw new SerializationException();
            }
        }
        private static void WritePrimitive(BinaryWriter writer, Type objectType, object value)
        {
            var code     = SerializerUtil.GetPrimitiveCode(objectType);
            var nullable = SerializerUtil.IsNullable(objectType);

            writer.Write(nullable);
            writer.Write((byte)code);

            if (value == null)
            {
                return;
            }

            switch (code)
            {
            case PrimitiveTypeCode.Boolean:
                writer.Write((bool)value);
                break;

            case PrimitiveTypeCode.Byte:
                writer.Write((byte)value);
                break;

            case PrimitiveTypeCode.SByte:
                writer.Write((sbyte)value);
                break;

            case PrimitiveTypeCode.Int16:
                writer.Write((short)value);
                break;

            case PrimitiveTypeCode.UInt16:
                writer.Write((ushort)value);
                break;

            case PrimitiveTypeCode.Int32:
                writer.Write((int)value);
                break;

            case PrimitiveTypeCode.UInt32:
                writer.Write((uint)value);
                break;

            case PrimitiveTypeCode.Int64:
                writer.Write((long)value);
                break;

            case PrimitiveTypeCode.UInt64:
                writer.Write((ulong)value);
                break;

            case PrimitiveTypeCode.Single:
                writer.Write((float)value);
                break;

            case PrimitiveTypeCode.Double:
                writer.Write((double)value);
                break;

            case PrimitiveTypeCode.Char:
                writer.Write((char)value);
                break;

            case PrimitiveTypeCode.String:
                writer.Write((string)value);
                break;
            }
        }