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); } }