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