public Tuple <MemberAndReader <FieldInfo>[], MemberAndReader <PropertyInfo>[]> GetFieldsAndProperties(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }

            if (_fieldAndPropertyCache.TryGetValue(type, out var cachedResult))
            {
                return(cachedResult);
            }

            return(_fieldAndPropertyCache.GetOrAdd(type, _reader.GetFieldsAndProperties(type)));
        }
        /// <summary>
        /// A member setter is a delegate that takes an instance and writes the data for fields and properties to a BinarySerialisationWriter. Note that it will not write the
        /// ObjectStart and ObjectEnd data because the caller should be responsible for tracking references (if the caller is configured to track references), which is tied to
        /// the ObjectStart data. If the caller is tracking references (to either reuse references that appear multiple times in the source data or to identify any circular
        /// references while performing serialisation) then member setters should only be generated for types whose fields and properties are types that do not support reference
        /// reuse, such as primitives and strings. Fields and properties that are primitives or strings or DateTime or TimeSpan or GUIDs (or one-dimensional arrays of any of those
        /// types) can be handled entirely by code within this method but it's also possible to provide member setters for other field or property types via the valueWriterRetriever
        /// argument - again, if the caller is tracking references then it should only pass through member setters via valueWriterRetriever that are for non-reference types, such as
        /// structs) because generating nested member setters in this manner will prevent any reference tracking.
        /// </summary>
        public static MemberSetterGenerationAttemptResult GetMemberSetterAvailability(Type type, IAnalyseTypesForSerialisation typeAnalyser, ValueWriterRetriever valueWriterRetriever)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }
            if (typeAnalyser == null)
            {
                throw new ArgumentNullException(nameof(typeAnalyser));
            }
            if (valueWriterRetriever == null)
            {
                throw new ArgumentNullException(nameof(valueWriterRetriever));
            }

            var sourceParameter = Expression.Parameter(type, "source");
            var writerParameter = Expression.Parameter(_writerType, "writer");
            var statements      = new List <Expression>();
            var fieldsSet       = new List <CachedNameData>();

            var(fields, properties) = typeAnalyser.GetFieldsAndProperties(type);
            var membersToConsiderSerialising =
                fields.Select(f =>
            {
                var fieldNameBytes = GetFieldNameBytesIfWantoSerialiseField(f.Member, type);
                if (fieldNameBytes == null)
                {
                    return(null);
                }
                return(new
                {
                    FieldNameBytes = fieldNameBytes,
                    Member = (MemberInfo)f.Member,
                    ValueWriterIfPossibleToGenerate = TryToGetValueWriterForMember(f.Member, f.Member.FieldType, sourceParameter, writerParameter, valueWriterRetriever)
                });
            })
                .Concat(
                    properties.Select(p =>
            {
                var fieldNameBytes = GetFieldNameBytesIfWantoSerialiseProperty(p.Member);
                if (fieldNameBytes == null)
                {
                    return(null);
                }
                return(new
                {
                    FieldNameBytes = fieldNameBytes,
                    Member = (MemberInfo)p.Member,
                    ValueWriterIfPossibleToGenerate = TryToGetValueWriterForMember(p.Member, p.Member.PropertyType, sourceParameter, writerParameter, valueWriterRetriever)
                });
            })
                    )
                .Where(member => member != null);                 // Ignore any fields or properties that we didn't want to serialise

            foreach (var member in membersToConsiderSerialising)
            {
                // If it wasn't possible to get a member setter for this field/property type then it's not possible to generate a member setter for the type that it's on
                if (member.ValueWriterIfPossibleToGenerate == null)
                {
                    return(MemberSetterGenerationAttemptResult.ForFailure(member.Member));
                }

                // Generate the write-FieldName-to-stream method call
                statements.Add(
                    Expression.Call(
                        writerParameter,
                        _writeBytesMethod,
                        Expression.Constant(new[] { (byte)BinarySerialisationDataType.FieldName }.Concat(member.FieldNameBytes.OnlyAsReferenceID).ToArray())
                        )
                    );

                // Write out the value-serialising expression
                statements.Add(member.ValueWriterIfPossibleToGenerate);
                fieldsSet.Add(member.FieldNameBytes);
            }

            // Group all of the field setters together into one call
            Expression body;

            if (statements.Count == 0)
            {
                body = Expression.Empty();
            }
            else if (statements.Count == 1)
            {
                body = statements[0];
            }
            else
            {
                body = Expression.Block(statements);
            }
            return(MemberSetterGenerationAttemptResult.ForSuccess(
                       new MemberSetterDetails(
                           type,
                           Expression.Lambda(
                               body,
                               sourceParameter,
                               writerParameter
                               ),
                           GetTypeNameBytes(type),
                           fieldsSet.ToArray()
                           )
                       ));
        }