public static void AddUnityFormatters(this SerializerConfig config) => config.OnResolveFormatter.Add(GetFormatter);
/// <summary> /// Creates a new CerasSerializer, be sure to check out the tutorial. /// </summary> public CerasSerializer(SerializerConfig config = null) { Config = config ?? new SerializerConfig(); if (Config.ExternalObjectResolver == null) { Config.ExternalObjectResolver = new ErrorResolver(); } if (Config.Advanced.UseReinterpretFormatter && Config.VersionTolerance == VersionTolerance.AutomaticEmbedded) { throw new NotSupportedException("You can not use 'UseReinterpretFormatter' together with version tolerance. Either disable version tolerance, or use the old formatter for blittable types by setting 'Config.Advanced.UseReinterpretFormatter' to false."); } TypeBinder = Config.Advanced.TypeBinder ?? new NaiveTypeBinder(); DiscardObjectMethod = Config.Advanced.DiscardObjectMethod; _userResolvers = Config.OnResolveFormatter.ToArray(); // Int, Float, Enum, ... _resolvers.Add(new PrimitiveResolver(this)); // Fast native copy for unmanaged types; // can not handle generic structs like ValueTuple<> because they always have to be ".IsAutoLayout" _resolvers.Add(new ReinterpretFormatterResolver(this)); // DateTime, Guid, KeyValuePair, Tuple, ... _resolvers.Add(new StandardFormatterResolver(this)); // Array, List, Dictionary, ICollection<T>, ... _resolvers.Add(new CollectionFormatterResolver(this)); // String Formatter should never be wrapped in a RefFormatter, that's too slow and not necessary IFormatter stringFormatter; if (Config.Advanced.SizeLimits.MaxStringLength < uint.MaxValue) { stringFormatter = new MaxSizeStringFormatter(Config.Advanced.SizeLimits.MaxStringLength); } else { stringFormatter = new StringFormatter(); } InjectDependencies(stringFormatter); SetFormatters(typeof(string), stringFormatter, stringFormatter); // // Type formatter is the basis for all complex objects, // It is special and has its own caching system (so no wrapping in a ReferenceFormatter) var typeFormatter = new TypeFormatter(this); var runtimeType = GetType().GetType(); SetFormatters(typeof(Type), typeFormatter, typeFormatter); SetFormatters(runtimeType, typeFormatter, typeFormatter); // MemberInfos (FieldInfo, RuntimeFieldInfo, ...) _resolvers.Add(new ReflectionFormatterResolver(this)); // DynamicObjectResolver is a special case, so it is not in the resolver-list // That is because we only want to have specific resolvers in the resolvers-list _dynamicResolver = new DynamicObjectFormatterResolver(this); // System.Linq.Expressions - mostly handled by special configurations and DynamicObjectFormatter, but there are some special cases. _resolvers.Add(new ExpressionFormatterResolver()); // // Basic setup is done // Now calculate the protocol checksum _knownTypes = Config.KnownTypes.ToArray(); if (Config.KnownTypes.Distinct().Count() != _knownTypes.Length) { // We want a *good* error message. Simply saying "can't contain any type multiple times" is not enough. // We have to figure out which types are there more than once. HashSet <Type> hashSet = new HashSet <Type>(); List <Type> foundDuplicates = new List <Type>(); for (int i = 0; i < _knownTypes.Length; i++) { var t = _knownTypes[i]; if (!hashSet.Add(t)) { if (!foundDuplicates.Contains(t)) { foundDuplicates.Add(t); } } } var duplicateTypesStr = string.Join(", ", foundDuplicates.Select(t => t.Name)); throw new Exception("KnownTypes can not contain any type multiple times! Your KnownTypes collection contains the following types more than once: " + duplicateTypesStr); } // // Generate checksum { foreach (var t in _knownTypes) { ProtocolChecksum.Add(t.FullName); if (t.IsEnum) { // Enums are a special case, they are classes internally, but they only have one field ("__value") // We're always serializing them in binary with their underlying type, so there's no reason changes like Adding/Removing/Renaming // enum-members could ever cause any binary incompatibility // // A change in the base-type however WILL cause problems! ProtocolChecksum.Add(t.GetEnumUnderlyingType().FullName); continue; } if (!t.ContainsGenericParameters) { var meta = GetTypeMetaData(t); if (meta.PrimarySchema != null) { foreach (var m in meta.PrimarySchema.Members) { ProtocolChecksum.Add(m.MemberType.FullName); ProtocolChecksum.Add(m.MemberName); foreach (var a in m.MemberInfo.GetCustomAttributes(true)) { ProtocolChecksum.Add(a.ToString()); } } } } } ProtocolChecksum.Finish(); } // // We can already pre-warm formatters // - dynamic serializers generate their code // - reference formatters generate their wrappers foreach (var t in _knownTypes) { if (!t.ContainsGenericParameters) { GetReferenceFormatter(t); } } // // Finally we need "instance data" _instanceDataPool = new FactoryPool <InstanceData>(p => { var d = new InstanceData(); d.CurrentRoot = null; d.ObjectCache = new ObjectCache(); d.TypeCache = new TypeCache(_knownTypes); d.EncounteredSchemaTypes = new HashSet <Type>(); return(d); }); InstanceData = _instanceDataPool.RentObject(); if (Config.Advanced.SealTypesWhenUsingKnownTypes) { if (_knownTypes.Length > 0) { typeFormatter.Seal(); } } }
RecursionMode _mode = RecursionMode.Idle; // while in one mode we cannot enter the others public CerasSerializer(SerializerConfig config = null) { Config = config ?? new SerializerConfig(); // Check if the config is even valid if (Config.EmbedChecksum && !Config.GenerateChecksum) { throw new InvalidOperationException($"{nameof(Config.GenerateChecksum)} must be true if {nameof(Config.EmbedChecksum)} is true!"); } if (Config.EmbedChecksum && Config.PersistTypeCache) { throw new InvalidOperationException($"You can't have '{nameof(Config.EmbedChecksum)}' and also have '{nameof(Config.PersistTypeCache)}' because adding new types changes the checksum. You can use '{nameof(Config.GenerateChecksum)}' alone, but the checksum might change after every serialization call..."); } if (Config.ExternalObjectResolver == null) { Config.ExternalObjectResolver = new ErrorResolver(); } _userResolver = Config.OnResolveFormatter; // Int, Float, Enum, String _resolvers.Add(new PrimitiveResolver(this)); _resolvers.Add(new ReflectionTypesFormatterResolver(this)); _resolvers.Add(new KeyValuePairFormatterResolver(this)); _resolvers.Add(new CollectionFormatterResolver(this)); // DateTime, Guid _resolvers.Add(new BclFormatterResolver()); // DynamicObjectResolver is a special case, so it is not in the resolver-list // That is because we only want to have specific resolvers in the resolvers-list _dynamicResolver = new DynamicObjectFormatterResolver(this); // Type formatter is the basis for all complex objects var typeFormatter = new TypeFormatter(this); _specificFormatters.Add(typeof(Type), typeFormatter); //if (Config.KnownTypes.Count > 0) // if (Config.SealTypesWhenUsingKnownTypes) // typeFormatter.Seal(); _typeFormatter = (IFormatter <Type>)GetSpecificFormatter(typeof(Type)); // // Basic setup is done // Now we're adding our "known types" // generating serializers and updating the protocol checksum // Config.KnownTypes.Seal(); foreach (var t in Config.KnownTypes) { if (Config.GenerateChecksum) { ProtocolChecksum.Add(t.FullName); if (t.IsEnum) { // Enums are a special case, they are classes internally, but they only have one field ("__value") // We're always serializing them in binary with their underlying type, so there's no reason changes like Adding/Removing/Renaming // enum-members could ever cause any binary incompatibility // // A change in the base-type however WILL cause problems! // // Note, even without this if() everything would be ok, since the code below also writes the field-type // but it is better to *explicitly* do this here and explain why we're doing it! ProtocolChecksum.Add(t.GetEnumUnderlyingType().FullName); continue; } var members = DynamicObjectFormatter <object> .GetSerializableMembers(t, Config.DefaultTargets, Config.ShouldSerializeMember); foreach (var m in members) { ProtocolChecksum.Add(m.MemberType.FullName); ProtocolChecksum.Add(m.Name); foreach (var a in m.MemberInfo.GetCustomAttributes(true)) { ProtocolChecksum.Add(a.ToString()); } } } } if (Config.GenerateChecksum) { ProtocolChecksum.Finish(); } // // Finally we need "instance data" _instanceDataPool = new FactoryPool <InstanceData>(p => { var d = new InstanceData(); d.CurrentRoot = null; d.ObjectCache = new ObjectCache(); d.TypeCache = new ObjectCache(); foreach (var t in Config.KnownTypes) { d.TypeCache.RegisterObject(t); // For serialization d.TypeCache.AddKnownType(t); // For deserialization } return(d); }); InstanceData = _instanceDataPool.RentObject(); }