public CachingTypeAnalyser(IAnalyseTypesForSerialisation reader) { _reader = reader ?? throw new ArgumentNullException(nameof(reader)); _typeLookupCache = new ConcurrentDictionary <string, Type>(); _typeBuilderCache = new ConcurrentDictionary <string, Func <object> >(); _fieldAndPropertyCache = new ConcurrentDictionary <Type, Tuple <MemberAndReader <FieldInfo>[], MemberAndReader <PropertyInfo>[]> >(); _requiredFieldCache = new ConcurrentDictionary <Type, FieldInfo[]>(); _fieldNameCache = new ConcurrentDictionary <Tuple <Type, string, string>, MemberAndWriter <FieldInfo> >(); _deprecatedPropertyCache = new ConcurrentDictionary <Tuple <Type, string, string, Type>, DeprecatedPropertySettingDetails>(); }
} // internal constructor for unit testing internal BinarySerialisationWriter( // internal constructor for unit testing and for FastestTreeBinarySerialisation Stream stream, ReferenceReuseOptions referenceReuseStrategy, IAnalyseTypesForSerialisation typeAnalyser, ConcurrentDictionary <Type, DeepCompiledMemberSettersGenerationResults> deepMemberSetterCacheIfEnabled) { _stream = stream ?? throw new ArgumentNullException(nameof(stream)); ReferenceReuseStrategy = referenceReuseStrategy; _typeAnalyser = typeAnalyser; _deepMemberSetterCacheIfEnabled = deepMemberSetterCacheIfEnabled; // Only expect this to be non-null when called by FastestTreeBinarySerialisation _recordedTypeNames = new Dictionary <Type, BinarySerialisationWriterCachedNames.CachedNameData>(); _encounteredFields = new Dictionary <Tuple <FieldInfo, Type>, BinarySerialisationWriterCachedNames.CachedNameData>(); _encounteredProperties = new Dictionary <PropertyInfo, BinarySerialisationWriterCachedNames.CachedNameData>(); _shouldSerialiseMemberCache = new Dictionary <Tuple <MemberInfo, Type>, bool>(); _haveStartedSerialising = false; }
internal BinarySerialisationReader(Stream stream, IDeserialisationTypeConverter[] typeConverters, IAnalyseTypesForSerialisation typeAnalyser) // internal constructor may be used by unit tests { _stream = stream ?? throw new ArgumentNullException(nameof(stream)); _typeConverters = typeConverters ?? throw new ArgumentNullException(nameof(typeConverters)); _typeAnalyser = typeAnalyser ?? throw new ArgumentNullException(nameof(typeAnalyser)); _nameReferences = new Dictionary <int, string>(); _objectReferences = new Dictionary <int, object>(); _deferredInitialisationReferenceIDsAwaitingPopulation = new HashSet <int>(); // We can take adantage of the fact that the fields for each distinct type will always appear in the same order (and there will always be the precise same number of // field entries for subsequent occurrences of a particular type) AND the field names will always use Name Reference IDs after the first time that a type is written // out. So, instead of treating the field data as dynamic content and having to try to resolve the fields each time that an object is to be deserialised, we can // build specialised deserialisers for each type that know what fields are going to appear and that have cached the field lookup data - this will mean that there is // a lot less work to do when there are many instances of a given type within a payload. Also note that after the first appearance of a type, Name Reference IDs will // always be used for the type name and so the lookup that is constructed to these per-type deserialisers has an int key (which is less work to match than using the // full type name as the key would be). // - This is confused if the BinarySerialisationWriter is optimised for wide circular references because it shifts object definitions around and so the type readers // can't be used in that case (which means that performance per-instance deserialisation performance is reduced but it would stack overflow otherwise, so it's not // really compromised since the optimised-for-tree approach might not work at all!) _typeReaders = new Dictionary <int, BinarySerialisationReaderTypeReader>(); _haveEncounteredDeferredInitialisationObject = false; }
/// <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() ) )); }
internal Serialiser(IAnalyseTypesForSerialisation typeAnalyser) // internal constructor is intended for unit testing only { _typeAnalyser = typeAnalyser ?? throw new ArgumentNullException(nameof(typeAnalyser)); }