public TypeSchema Initialize(KnownSerializers serializers, TypeSchema targetSchema) { var runtimeSchema = TypeSchema.FromType(typeof(T), serializers.RuntimeVersion, this.GetType(), Version); var members = runtimeSchema.GetCompatibleMemberSet(targetSchema); this.deserializeImpl = Generator.GenerateDeserializeMethod <T>(il => Generator.EmitDeserializeFields(typeof(T), serializers, il, members)); this.serializeImpl = Generator.GenerateSerializeMethod <T>(il => Generator.EmitSerializeFields(typeof(T), serializers, il, members)); this.cloneImpl = Generator.GenerateCloneMethod <T>(il => Generator.EmitCloneFields(typeof(T), serializers, il)); this.clearImpl = Generator.GenerateClearMethod <T>(il => Generator.EmitClearFields(typeof(T), serializers, il)); return(targetSchema ?? runtimeSchema); }
public TypeSchema Initialize(KnownSerializers serializers, TypeSchema targetSchema) { if (targetSchema?.Version <= 2) { // maintain backward compatibility with older serialized data this.innerSerializer = new ClassSerializer <MemoryStream>(); } else { // otherwise default to the new implementation this.innerSerializer = new MemoryStreamSerializerImpl(); } return(this.innerSerializer.Initialize(serializers, targetSchema)); }
/// <summary> /// Validate whether two schemas are compatible. /// </summary> /// <remarks>Schemas are compatible if all required fields are present in both (regardless of type).</remarks> /// <param name="other">Other type schema.</param> public void ValidateCompatibleWith(TypeSchema other) { if (this.IsPartial || other == null || other.IsPartial) { return; } if ((this.flags & TypeFlags.IsClass) != 0 && (other.flags & TypeFlags.IsStruct) != 0) { throw new SerializationException($"The type {this.TypeName} changed between versions from struct to class, which is not supported."); } else if ((this.flags & TypeFlags.IsStruct) != 0 && (other.flags & TypeFlags.IsClass) != 0) { throw new SerializationException($"The type {this.TypeName} changed between versions from class to struct, which is not supported."); } // required members in this schema must be present in the other schema var requiredAndMissing = this.Members.Where(mbr => mbr.IsRequired && !other.map.ContainsKey(mbr.Name)); if (requiredAndMissing.Count() > 0) { if (other.TypeName != this.TypeName) { throw new SerializationException($"The schema {other.Name} version {other.Version} (implemented by {other.TypeName}) is missing the following members required in the current version of {this.TypeName}: {string.Join(",", requiredAndMissing)}"); } else { throw new SerializationException($"The type {this.TypeName} appears to have changed in a way that makes it incompatible with previous versions. The following members required by the new version are missing: {string.Join(",", requiredAndMissing)}"); } } // all members in the other schema need to be present in this schema requiredAndMissing = other.Members.Where(o => !this.map.ContainsKey(o.Name)); if (requiredAndMissing.Count() > 0) { if (other.TypeName != this.TypeName) { throw new SerializationException($"The schema {other.Name} version {other.Version} (implemented by {other.TypeName}) contains the following members which are not present in the current version of {this.TypeName}: {string.Join(",", requiredAndMissing)}"); } else { throw new SerializationException($"The type {this.TypeName} appears to have changed in a way that makes it incompatible with previous versions. The following members required by the old version are missing in the new version: {string.Join(",", requiredAndMissing)}"); } } }
/// <summary> /// Captures the schema provided by a persisted store. /// </summary> /// <param name="schema">The schema to register.</param> internal void RegisterSchema(TypeSchema schema) { var id = schema.Id; // skip if a handler is already registered for this id if (this.handlersById.ContainsKey(id)) { return; } if (schema.IsPartial && this.schemasById.TryGetValue(id, out TypeSchema otherSchema)) { // schema is already registered return; } // store the schema and override whatever is present already this.schemas[schema.Name] = schema; this.schemasById[schema.Id] = schema; }
/// <summary> /// Registers a given type with the specified contract name. /// Use this overload to deserialize data persisted before a type name change. /// </summary> /// <param name="type">The type to use when deserializing objects with the specified contract.</param> /// <param name="contractName">The name to remap. This can be a full type name or a contract name.</param> /// <param name="cloningFlags">Optional flags that control the cloning behavior for this type.</param> public void Register(Type type, string contractName, CloningFlags cloningFlags = CloningFlags.None) { contractName = contractName ?? TypeSchema.GetContractName(type, this.runtimeVersion); if (this.knownTypes.TryGetValue(contractName, out Type existingType) && existingType != type) { throw new SerializationException($"Cannot register type {type.AssemblyQualifiedName} under the contract name {contractName} because the type {existingType.AssemblyQualifiedName} is already registered under the same name."); } if (this.cloningFlags.TryGetValue(type, out var existingFlags) || this.handlersByType.ContainsKey(type)) { // cannot re-register once type flags has been registered or handler has been created if (existingFlags != cloningFlags) { throw new SerializationException($"Cannot register type {type.AssemblyQualifiedName} with cloning flags ({cloningFlags}) because a handler for it has already been created with flags ({existingFlags})."); } } this.knownTypes[contractName] = type; this.knownNames[type] = contractName; this.cloningFlags[type] = cloningFlags; }
/// <summary> /// Retrieves the <see cref="MemberInfo"/> information for each member of the type schema, based on a target schema specification. /// </summary> /// <param name="targetSchema">The schema specification describing which members and in which order to enumerate. /// If null, all members are returned in their original order.</param> /// <returns>A collection of <see cref="MemberInfo"/> objects.</returns> public IEnumerable <MemberInfo> GetCompatibleMemberSet(TypeSchema targetSchema = null) { if (targetSchema == null || targetSchema.IsPartial) { return(this.Members.Select(t => t.MemberInfo)); } this.ValidateCompatibleWith(targetSchema); var set = new List <MemberInfo>(targetSchema.Members.Length); foreach (var o in targetSchema.Members) { if (this.map.TryGetValue(o.Name, out TypeMemberSchema ms)) { set.Add(ms.MemberInfo); } } return(set); }
// helper fn to deal with v0 "schema" ([id, type] pairs) internal void RegisterMetadata(IEnumerable <Metadata> metadata) { foreach (var meta in metadata) { if (meta.Kind == MetadataKind.StreamMetadata) { var sm = meta as PsiStreamMetadata; if (sm.RuntimeTypes != null) { // v0 has runtime types affixed to each stream metadata foreach (var kv in sm.RuntimeTypes) { var schema = new TypeSchema(kv.Value, kv.Key, kv.Value, 0); this.RegisterSchema(schema); } } } else if (meta.Kind == MetadataKind.TypeSchema) { this.RegisterSchema((TypeSchema)meta); } } }
/// <inheritdoc /> public TypeSchema Initialize(KnownSerializers serializers, TypeSchema targetSchema) { // Add a comparerHandler of type IEqualityComparer. This will take care of serializing, // deserializing and cloning the Comparer member of Dictionary. Because the comparer field // is private, we will also need to generate a dynamic method so that we can set the // comparer field upon deserialization or cloning. This method should be invoked right after // clearing the target Dictionary, before adding the deserialized or cloned entries to it. this.comparerHandler = serializers.GetHandler <IEqualityComparer <TKey> >(); this.setComparerImpl = this.GenerateSetComparerMethod(); // Use an array serializer to serialize the dictionary elements as an array of key-value pairs this.entriesHandler = serializers.GetHandler <KeyValuePair <TKey, TValue>[]>(); var type = typeof(Dictionary <TKey, TValue>); var name = TypeSchema.GetContractName(type, serializers.RuntimeVersion); // Treat the Dictionary as a class with 2 members - a comparer and an array of key-value pairs var comparerMember = new TypeMemberSchema("Comparer", typeof(IEqualityComparer <TKey>).AssemblyQualifiedName, true); var entriesMember = new TypeMemberSchema("KeyValuePairs", typeof(KeyValuePair <TKey, TValue>[]).AssemblyQualifiedName, true); var schema = new TypeSchema(name, TypeSchema.GetId(name), type.AssemblyQualifiedName, TypeFlags.IsClass, new[] { comparerMember, entriesMember }, SchemaVersion); return(targetSchema ?? schema); }
public TypeSchema Initialize(KnownSerializers serializers, TypeSchema targetSchema) { return(null); }
/// <summary> /// Creates and registers a handler for the specified type according to the rules added so far. /// </summary> /// <typeparam name="T">The type being serialized.</typeparam> /// <returns>The newly created handler.</returns> private SerializationHandler AddHandler <T>() { SerializationHandler handler = null; var type = typeof(T); ISerializer <T> serializer = null; TypeSchema schema = null; if (!this.knownNames.TryGetValue(type, out string name)) { name = TypeSchema.GetContractName(type, this.runtimeVersion); } if (!this.schemas.TryGetValue(name, out schema)) { // try to match to an existing schema without assembly/version info string typeName = TypeResolutionHelper.RemoveAssemblyName(type.AssemblyQualifiedName); schema = this.schemas.Values.FirstOrDefault(s => TypeResolutionHelper.RemoveAssemblyName(s.TypeName) == typeName); } int id = schema?.Id ?? TypeSchema.GetId(name); serializer = this.CreateSerializer <T>(); handler = SerializationHandler.Create <T>(serializer, schema?.Name ?? name, id); // first register the handler int oldCount = this.handlers.Length; var newHandlers = new SerializationHandler[oldCount + 1]; Array.Copy(this.handlers, newHandlers, oldCount); newHandlers[oldCount] = handler; this.handlers = newHandlers; var newIndex = new Dictionary <SerializationHandler, int>(this.index); newIndex[handler] = oldCount; this.index = newIndex; var newHandlersByType = new Dictionary <Type, SerializationHandler>(this.handlersByType); newHandlersByType[type] = handler; this.handlersByType = newHandlersByType; var newHandlersById = new Dictionary <int, SerializationHandler>(this.handlersById); newHandlersById[handler.Id] = handler; this.handlersById = newHandlersById; // find the schema for this serializer (can be null for interfaces) if (serializer != null) { // initialize the serializer after the handler is registered, // to make sure all handlers are registered before initialization runs and // allow the serializer initialization code to find and cache the handlers for the types it needs schema = serializer.Initialize(this, schema); // let any subscribers know that we initialized a new serializer that publishes a schema if (schema != null) { // store the updated schema and override whatever is present already this.schemas[schema.Name] = schema; this.schemasById[schema.Id] = schema; this.SchemaAdded?.Invoke(this, schema); } } return(handler); }
public TypeSchema Initialize(KnownSerializers serializers, TypeSchema targetSchema) { // schema is not used, since the behavior of arrays is hard-coded return(null); }
public TypeSchema Initialize(KnownSerializers serializers, TypeSchema targetSchema) { return(targetSchema ?? TypeSchema.FromType(typeof(string), serializers.RuntimeVersion, this.GetType(), Version)); }