private static void GenerateMemberSettersForTypeIfPossible(
            Type type,
            HashSet <Type> typesEncountered,
            HashSet <CachedNameData> typeNamesToDeclare,
            HashSet <CachedNameData> fieldNamesToDeclare,
            Dictionary <Type, MemberSetterDetails> memberSetters,
            IFastSerialisationTypeConverter[] typeConverters,
            MemberInfo memberIfAny,
            bool throwAtFirstHurdle)
        {
            // Leave primitive-like values to the BinarySerialisationWriter's specialised methods (Boolean, String, DateTime, etc..)
            if (Serialiser.IsTreatedAsPrimitive(type))
            {
                return;
            }

            // Ordinarily, we want to only consider consistent concrete types (sealed classes and structs, essentially) - if a field has type MyClass and MyClass is a
            // non-sealed class then we might generate a member setter for MyClass but then encounter a MyDerivedClass at runtime that inherits from MyClass but has
            // three extra fields; those extra fields would not be serialised, then. If the field has a type that is a class that is sealed then we know that this
            // can't happen and so we're safe (and structs don't support inheritance, so we're safe with those as well).
            var isPredictableType = (type.IsClass && type.IsSealed) || type.IsValueType;

            if (!isPredictableType)
            {
                if (throwAtFirstHurdle)
                {
                    ThrowNonOptimalFastestTreeSerialisationException(type, memberIfAny);
                }
                return;
            }
            if (typesEncountered.Contains(type))
            {
                return;
            }

            // We don't need additional member setters for array types because the BinarySerialisationCompiledMemberSetters can use a member setter for type A when
            // serialising instances of type A *and* when serialising arrays of A (it currently only tackle one-dimensional arrays but that could potentially change
            // in the future)
            if (type.IsArray)
            {
                GenerateMemberSettersForTypeIfPossible(type.GetElementType(), typesEncountered, typeNamesToDeclare, fieldNamesToDeclare, memberSetters, typeConverters, memberIfAny, throwAtFirstHurdle);
                return;
            }

            // Check the type converters here - if any of them would change the current type then we need to try to generate a member setter for the converted-to type
            // (rather than the current type). This check is done after the IsArray check above because the Serialiser has special handling for arrays that occurs before
            // handling for objects and so it's not possible to have a type converter than changes an array of T into something else.
            var(_, convertedToType) = TryToGetValueWriterViaTypeConverters(type);
            if (convertedToType != type)
            {
                typesEncountered.Add(type);
                type = convertedToType;
            }

            // In order to generate a member setter for Type A, we might need member setters for Types B and C and for them we might need member setters for Types
            // D and E, so we need to dig down to the bottom of the trees and work our way back up before we start trying to call TryToGenerateMemberSetter
            typesEncountered.Add(type);
            var(fields, properties) = DefaultTypeAnalyser.Instance.GetFieldsAndProperties(type);
            foreach (var field in fields)
            {
                if (field.Member.FieldType.IsPointer())
                {
                    if (throwAtFirstHurdle)
                    {
                        ThrowNonOptimalFastestTreeSerialisationException(type, memberIfAny);
                    }
                    continue;
                }
                if (GetFieldNameBytesIfWantoSerialiseField(field.Member, type) != null)
                {
                    GenerateMemberSettersForTypeIfPossible(field.Member.FieldType, typesEncountered, typeNamesToDeclare, fieldNamesToDeclare, memberSetters, typeConverters, field.Member, throwAtFirstHurdle);
                }
            }
            foreach (var property in properties)
            {
                if (property.Member.PropertyType.IsPointer())
                {
                    if (throwAtFirstHurdle)
                    {
                        ThrowNonOptimalFastestTreeSerialisationException(type, memberIfAny);
                    }
                    continue;
                }
                if (GetFieldNameBytesIfWantoSerialiseProperty(property.Member) != null)
                {
                    GenerateMemberSettersForTypeIfPossible(property.Member.PropertyType, typesEncountered, typeNamesToDeclare, fieldNamesToDeclare, memberSetters, typeConverters, property.Member, throwAtFirstHurdle);
                }
            }

            var memberSetterGenerationResult = GetMemberSetterAvailability(
                type,
                DefaultTypeAnalyser.Instance,
                t =>
            {
                // Give the type converters a shot - if TryToGetValueWriterViaTypeConverters returns a non-null reference then one of them wants to control
                // the serialisation of this type (if null is returned then continue on to process as normal)
                var(valueWriterViaTypeConverter, _) = TryToGetValueWriterViaTypeConverters(t);
                if (valueWriterViaTypeConverter != null)
                {
                    return(valueWriterViaTypeConverter);
                }

                if (memberSetters.TryGetValue(t, out var valueWriter) && (valueWriter != null))
                {
                    // The TryToGenerateMemberSetter method has requested a member setter for one of the fields or properties and we have access to a member
                    // setter that matches that type precisely - this is the ideal case!
                    return(ValueWriter.PopulateValue(valueWriter.MemberSetter));
                }

                return(null);
            }
                );

            if (memberSetterGenerationResult.MemberSetterDetailsIfSuccessful != null)
            {
                typeNamesToDeclare.Add(memberSetterGenerationResult.MemberSetterDetailsIfSuccessful.TypeName);
                foreach (var fieldName in memberSetterGenerationResult.MemberSetterDetailsIfSuccessful.FieldsSet)
                {
                    fieldNamesToDeclare.Add(fieldName);
                }
                memberSetters.Add(type, memberSetterGenerationResult.MemberSetterDetailsIfSuccessful);
            }
            else
            {
                if (throwAtFirstHurdle)
                {
                    ThrowNonOptimalFastestTreeSerialisationException(type, memberIfAny);
                }

                // If we were unable to generate a member setter for this type then record the null value - it's useful for the GetMemberSettersFor method to be
                // able to return member setters for types that it could generate them for but it's also useful to make it clear what types it was NOT possible
                // to generate them for because it can save the caller some work (they won't waste time trying to generate a member setter themselves)
                memberSetters.Add(type, null);
            }

            (ValueWriter, Type) TryToGetValueWriterViaTypeConverters(Type t)
            {
                foreach (var typeConverter in typeConverters)
                {
                    var typeNames           = new List <CachedNameData>();
                    var fieldNames          = new List <CachedNameData>();
                    var typeConverterWriter = typeConverter.GetDirectWriterIfPossible(
                        t,
                        requestedMemberSetterType =>
                    {
                        var requestedMemberSetterTypeWriter =
                            GetMemberSetterAvailability(
                                requestedMemberSetterType,
                                DefaultTypeAnalyser.Instance,
                                nestedType => (memberSetters.TryGetValue(nestedType, out var valueWriter) && (valueWriter != null))
                                                                                ? ValueWriter.PopulateValue(valueWriter.MemberSetter)
                                                                                : null
                                )
                            .MemberSetterDetailsIfSuccessful;
                        if (requestedMemberSetterTypeWriter != null)
                        {
                            typeNames.Add(requestedMemberSetterTypeWriter.TypeName);
                            fieldNames.AddRange(requestedMemberSetterTypeWriter.FieldsSet);
                        }
                        return(requestedMemberSetterTypeWriter);
                    }
                        );
                    if (typeConverterWriter != null)
                    {
                        if (typeConverterWriter.MemberSetterIfNotSettingToDefault == null)
                        {
                            return(ValueWriter.SetValueToDefault, typeConverterWriter.ConvertedToType);
                        }

                        var newTypeName = GetTypeNameBytes(typeConverterWriter.ConvertedToType);
                        typeNamesToDeclare.Add(newTypeName);
                        foreach (var typeName in typeNames)
                        {
                            typeNamesToDeclare.Add(typeName);
                        }
                        foreach (var fieldName in fieldNames)
                        {
                            fieldNamesToDeclare.Add(fieldName);
                        }
                        return(ValueWriter.OverrideValue(newTypeName, typeConverterWriter.MemberSetterIfNotSettingToDefault), typeConverterWriter.ConvertedToType);
                    }
                }