// amf0 object
            object ReadObject()
            {
                var type = b.ReadUtf();

                if (context.HasConcreteType(type))
                {
                    var instance = context.CreateInstance(type);
                    var klass    = context.GetClassInfo(instance);

                    refs.Add(instance);

                    foreach (var pair in ReadItems())
                    {
                        if (klass.TryGetMember(pair.key, out var member))
                        {
                            member.SetValue(instance, pair.value);
                        }
                    }

                    return(instance);
                }
                else if (context.AsObjectFallback)
                {
                    // object reference added in this.ReadAmf0AsObject()
                    var obj = ReadAsObject();

                    obj.TypeName = type;
                    return(obj);
                }
                else
                {
                    throw new ArgumentException($"can't deserialize object: the type \"{type}\" isn't registered, and anonymous object fallback has been disabled");
                }
            }
            void WriteTypedObject(object value)
            {
                CheckDebug.NotNull(value);
                ReferenceAdd(value);

                var klass = context.GetClassInfo(value);

                WriteMarker(Marker.TypedObject);
                b.WriteUtfPrefixed(klass.Name);

                foreach (var member in klass.Members)
                {
                    b.WriteUtfPrefixed(member.Name);
                    WriteItem(member.GetValue(value));
                }

                // object end is marked with a zero-length field name, and an end of object marker.
                b.WriteUInt16(0);
                WriteMarker(Marker.ObjectEnd);
            }
            void WriteObject(object obj)
            {
                CheckDebug.NotNull(obj);

                WriteMarker(Marker.Object);
                if (ObjectReferenceAddOrWrite(obj))
                {
                    return;
                }

                var info = context.GetClassInfo(obj);

                if (refClasses.Add(info, out var index))
                {
                    // http://download.macromedia.com/pub/labs/amf/amf3_spec_121207.pdf
                    // """
                    // The first (low) bit is a flag with value 1. The second bit is a flag
                    // (representing whether a trait reference follows) with value 0 to imply that
                    // this objects traits are being sent by reference. The remaining 1 to 27
                    // significant bits are used to encode a trait reference index (an integer).
                    // -- AMF3 specification, 3.12 Object type
                    // """

                    // <u27=trait-reference-index> <0=trait-reference> <1=object-inline>
                    WriteInlineHeaderValue(index << 1);
                }
                else
                {
                    // write the class definition
                    // we can use the same format to serialize normal and extern classes, for simplicity's sake.
                    //     normal:         <u25=member-count>  <u1=dynamic>       <0=externalizable> <1=trait-inline> <1=object-inline>
                    //     externalizable: <u25=insignificant> <u1=insignificant> <1=externalizable> <1=trait-inline> <1=object-inline>
                    var header = info.Members.Length;
                    header = (header << 1) | (info.IsDynamic        ? 1 : 0);
                    header = (header << 1) | (info.IsExternalizable ? 1 : 0);
                    header = (header << 1) | 1;

                    // the final shift is done here.
                    WriteInlineHeaderValue(header);

                    // write the type name
                    UnmarkedWriteString(info.Name, isString: true);

                    // then, write the actual object value
                    if (info.IsExternalizable)
                    {
                        if (!(obj is IExternalizable externalizable))
                        {
                            throw new ArgumentException($"{obj.GetType().FullName} ({info.Name}) is marked as externalizable but does not implement IExternalizable");
                        }

                        externalizable.WriteExternal(new DataOutput(writer));
                    }
                    else
                    {
                        foreach (var member in info.Members)
                        {
                            UnmarkedWriteString(member.Name, isString: true);
                        }

                        foreach (var member in info.Members)
                        {
                            WriteItem(member.GetValue(obj));
                        }

                        if (info.IsDynamic)
                        {
                            if (!(obj is IDictionary <string, object> dictionary))
                            {
                                throw new ArgumentException($"{obj.GetType()} is marked as dynamic but does not implement IDictionary");
                            }

                            foreach (var(key, value) in dictionary)
                            {
                                UnmarkedWriteString(key, isString: true);
                                WriteItem(value);
                            }

                            UnmarkedWriteString(string.Empty, isString: true);
                        }
                    }
                }
            }