private static SerializerConfig GetConfig() { SerializerConfig config = new SerializerConfig(); config.DefaultTargets = TargetMember.AllProperties; //config.ConfigType<MapState>().ConfigMember(ms => ms.TalkingNation).Exclude().ConstructBy(typeof(MapState).GetConstructors()[0]); config.ConfigType <Nation>().ConstructBy(typeof(Nation).GetConstructors()[0]); config.ConfigType <WarSide>().ConstructBy(typeof(WarSide).GetConstructors()[0]); config.ConfigType <PixelData>().ConstructBy(typeof(PixelData).GetConstructors()[0]); config.ConfigType <UnorderedBytePair>().ConstructBy(typeof(UnorderedBytePair).GetConstructors()[0]); return(config); }
public Ceras(bool forNetworking = true) { if (CerasBufferPool.Pool == null) { CerasBufferPool.Pool = new CerasDefaultBufferPool(); } mSerializerConfig = new SerializerConfig { PreserveReferences = false }; mSerializerConfig.Advanced.SealTypesWhenUsingKnownTypes = forNetworking; if (forNetworking) { mSerializerConfig.VersionTolerance.Mode = VersionToleranceMode.Disabled; mSerializerConfig.KnownTypes.AddRange(KnownTypes); mSerializerConfig.KnownTypes.ForEach( knownType => mSerializerConfig.ConfigType(knownType).TypeConstruction = TypeConstruction.ByUninitialized() ); } else { mSerializerConfig.VersionTolerance.Mode = VersionToleranceMode.Standard; } mSerializer = new CerasSerializer(mSerializerConfig); }
static void CustomComparerFormatter() { // Our HashSet<byte> is losing its Comparer // We use a custom formatter to fix it SerializerConfig config = new SerializerConfig(); config.OnResolveFormatter.Add((c, t) => { if (t == typeof(HashSet <byte[]>)) { return(new HashSetFormatterThatKeepsItsComparer()); } return(null); // continue searching }); config.ConfigType <HashSet <byte[]> >() .SetFormatter(new HashSetFormatterThatKeepsItsComparer()); var ceras = new CerasSerializer(config); var set = new HashSet <byte[]>(new CustomComparer()); set.Add(new byte[] { 1, 2, 3 }); set.Add(new byte[] { 4, 5, 6 }); var clone = ceras.Deserialize <HashSet <byte[]> >(ceras.Serialize(set)); Debug.Assert(clone.Comparer.GetType() == typeof(CustomComparer)); }
private void AddKnownTypes(SerializerConfig config, string nameSpce) { var packetTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.Namespace == nameSpce).ToList(); foreach (var typ in packetTypes) { config.KnownTypes.Add(typ); mSerializerConfig.ConfigType(typ).TypeConstruction = TypeConstruction.ByUninitialized(); } }
public void CanNotConfigurePrimitives() { // Changing any settings for "Serialization Primitives" should not be allowed // String, Type, int, ... SerializerConfig config = new SerializerConfig(); HashSet <Type> primitiveTypes = new HashSet <Type> { typeof(Type), typeof(byte), typeof(int), typeof(float), typeof(string), }; bool configGotCalled = false; config.OnConfigNewType = t => { if (primitiveTypes.Contains(t.Type)) { configGotCalled = true; } }; // Configuring primitives should not be possible foreach (var t in primitiveTypes) { ExpectException(() => config.ConfigType(t)); } // Enum is not a real type (it's an abstract base class) ExpectException(() => config.ConfigType(typeof(Enum))); if (configGotCalled) { throw new Exception("on config new type should not be called for 'serialization primitives'"); } }
private void ReadData(byte[] buffer) { var config = new SerializerConfig(); config.VersionTolerance.Mode = VersionToleranceMode.Standard; config.ConfigType <VersionTest>().CustomResolver = (c, t) => new Ceras.Versioning.DynamicEmulator <VersionTest>(c, c.GetTypeMetaData(t).PrimarySchema); CerasSerializer ceras = new CerasSerializer(config); var clone = ceras.Deserialize <VersionTest>(buffer); }
public void CustomFormatterForEnum() { var config = new SerializerConfig(); config.ConfigType <DayOfWeek>().CustomFormatter = new DayOfWeekFormatter(); var ceras = new CerasSerializer(config); Assert.True(ceras.Deserialize <DayOfWeek>(ceras.Serialize(DayOfWeek.Sunday)) == DayOfWeek.Sunday); Assert.True(ceras.Deserialize <DayOfWeek>(ceras.Serialize(DayOfWeek.Monday)) == DayOfWeek.Monday); Assert.True(ceras.Deserialize <DayOfWeek>(ceras.Serialize(DayOfWeek.Saturday)) == DayOfWeek.Saturday); Assert.True(ceras.Deserialize <DayOfWeek>(ceras.Serialize((DayOfWeek)591835)) == (DayOfWeek)591835); }
public void WarnUserAgainstPropertyStruct() { Assert.Throws <WarningException>(() => { var config = new SerializerConfig(); config.VersionTolerance.Mode = VersionToleranceMode.Standard; var tc = config.ConfigType <PropertyStruct>(); CerasSerializer ceras = new CerasSerializer(config); var obj = new PropertyStruct(); obj.DeviceAddress = 1235; obj.SwitchId = 45454545; var data = ceras.Serialize(obj); var clone = ceras.Deserialize <PropertyStruct>(data); }); }
public DeserializationImmutablePoco() { var config = new SerializerConfig { DefaultTargets = TargetMember.AllFields, PreserveReferences = false }; config.Advanced.ReadonlyFieldHandling = ReadonlyFieldHandling.ForcedOverwrite; config.Advanced.SkipCompilerGeneratedFields = false; config.ConfigType <ImmutablePoco>().ConstructByUninitialized(); ceras = new CerasSerializer(config); for (int i = 0; i < 1000; ++i) { _t1.Add(new ImmutablePoco("hello", 123, Guid.NewGuid(), DateTime.Now)); } //_hyperion.Serialize(_t1, _m2); }
public void DerivedProperties() { var config = new SerializerConfig(); config.VersionTolerance.Mode = VersionToleranceMode.Standard; CerasSerializer ceras = new CerasSerializer(config); var obj = new DerivedClass(); obj.Name = "derived!"; var data = ceras.Serialize(obj); var clone = ceras.Deserialize <DerivedClass>(data); Assert.True(clone.Name == obj.Name); Assert.True(config.ConfigType <DerivedClass>().Members.Count(m => m.Member is PropertyInfo) == 1); }
private static void TryConfig3(DateTime dt, Foo foo) { try { var cfg = new SerializerConfig() { PreserveReferences = false }; cfg.ConfigType <DateTime>() .ConfigField("dateData").Include(); SerAndDeser(dt, foo, cfg); } catch (Ceras.Exceptions.WarningException ex) { throw ex; //Shouldn't be happening } catch (Exception ex) { throw ex; //Shouldn't be happening } }
void KnownColorFormatter() { SerializerConfig config = new SerializerConfig(); config.ConfigType <Color>() .CustomFormatter = new ColorFormatter(); var colors = new Color[] { Color.Azure, Color.FromArgb(255, 50, 150, 10), Color.FromArgb(255, 255, 255, 255), Color.White, }; var ceras = new CerasSerializer(config); var clone = ceras.Deserialize <Color[]>(ceras.Serialize(colors)); for (int i = 0; i < colors.Length; i++) { Assert.True(colors[i] == clone[i]); Assert.True(colors[i].Equals(clone[i])); } }
private static void TryConfig2(DateTime dt, Foo foo) { try { var cfg = new SerializerConfig() { PreserveReferences = false }; cfg.ConfigType <DateTime>() .CustomResolver = (c, t) => c.Advanced .GetFormatterResolver <Ceras.Resolvers.DynamicObjectFormatterResolver>() .GetFormatter(t); SerAndDeser(dt, foo, cfg); } catch (Ceras.Exceptions.WarningException ex) { throw ex; //Shouldn't be happening } catch (Exception ex) { throw ex; //Shouldn't be happening } }
public void TestDirectPoolingMethods() { var pool = new InstancePoolTest(); // Test: Ctor with argument { SerializerConfig config = new SerializerConfig(); config.ConfigType <Person>() // Select ctor, not delegate .ConstructBy(() => new Person("name")); var clone = DoRoundTripTest(config); Assert.True(clone != null); Assert.True(clone.Name.StartsWith("riki")); Assert.True(clone.Name.EndsWith(Person.CtorSuffix)); } // Test: Manual config { SerializerConfig config = new SerializerConfig(); config.ConfigType <Person>() .ConstructBy(TypeConstruction.ByStaticMethod(() => StaticPoolTest.CreatePerson())); var clone = DoRoundTripTest(config); Assert.True(clone != null); } // Test: Normal ctor, but explicitly { SerializerConfig config = new SerializerConfig(); config.ConfigType <Person>() // Select ctor, not delegate .ConstructBy(() => new Person()); var clone = DoRoundTripTest(config); Assert.True(clone != null); } // Test: Construct from instance-pool { SerializerConfig config = new SerializerConfig(); config.ConfigType <Person>() // Instance + method select .ConstructBy(pool, () => pool.CreatePerson()); var clone = DoRoundTripTest(config); Assert.True(clone != null); Assert.True(pool.IsFromPool(clone)); } // Test: Construct from static-pool { SerializerConfig config = new SerializerConfig(); config.ConfigType <Person>() // method select .ConstructBy(() => StaticPoolTest.CreatePerson()); var clone = DoRoundTripTest(config); Assert.True(clone != null); Assert.True(StaticPoolTest.IsFromPool(clone)); } // Test: Construct from any delegate (in this example: a lambda expression) { SerializerConfig config = new SerializerConfig(); Person referenceCapturedByLambda = null; config.ConfigType <Person>() // Use delegate .ConstructByDelegate(() => { var obj = new Person(); referenceCapturedByLambda = obj; return(obj); }); var clone = DoRoundTripTest(config); Assert.True(clone != null); Assert.True(ReferenceEquals(clone, referenceCapturedByLambda)); } // Test: Construct from instance-pool, with parameter { SerializerConfig config = new SerializerConfig(); config.ConfigType <Person>() // Use instance + method selection .ConstructBy(pool, () => pool.CreatePersonWithName("abc")); var clone = DoRoundTripTest(config); Assert.True(clone != null); Assert.True(clone.Name.StartsWith("riki")); Assert.True(pool.IsFromPool(clone)); } // Test: Construct from static-pool, with parameter { SerializerConfig config = new SerializerConfig(); config.ConfigType <Person>() // Use instance + method selection .ConstructBy(() => StaticPoolTest.CreatePersonWithName("abc")); var clone = DoRoundTripTest(config); Assert.True(clone != null); Assert.True(clone.Name.StartsWith("riki")); Assert.True(StaticPoolTest.IsFromPool(clone)); } }
public void Step3_Recycling() { /* * Scenario: * - You might be developing a game or other timing-sensitive application * where performance is extremely important and where small stutters by the GC have to be avoided * - You have your own object-pool and want Ceras to obtain instances from it and return them to it. * * What you want: * - You want more performance * - You want to decrease GC pauses and GC pressure * * What can we do: * - Recycle objects, reducing GC pressure * - Take objects from a pool instead of doing "new MyThing()" to improve cache-coherence. */ // Assuming we're receiving "network packets" over the internet, we'd instantiate a new object every time we call Deserialize // That is pretty wasteful. We receive one object, deserialize it, use it, then discard it, ... // // What we can do is have one instance that we can just overwrite all the time! // // Ceras will use the provided object and overwrite the fields; instead of creating a new object instance. // If no instance is provided, Ceras will just instantiate one. // // Hint: This can also be used to quickly set or reset an object to some predefined values in other scenarios. var serializer = new CerasSerializer(); Person recycledPerson = new Person { Health = 35, Name = "test" }; byte[] buffer = serializer.Serialize(recycledPerson); // Lets assume we already got a network buffer and now we just have to read it. for (int i = 0; i < 100; i++) { // No person object will be allocated, the fields // of 'recycledPerson' will just be overwritten serializer.Deserialize <Person>(ref recycledPerson, buffer); } // // Now we'll use some extremely simple object-pooling solution to reduce GC pressure. // The 'MyVerySimplePool' is obviously just for illustration, in something like Unity3D you would // of course make something much more elaborate... // // If the data in the buffer tells us "it's a 'null' object" then Ceras will of course set 'recycledPerson' to null. // But now you might wonder what happens to the instance that was previously inside 'recycledPerson'. // Normally the answer would be that the object would be simply lost (and eventually its space would be reclaimed by the .NET "garbage-collector"). // // In some scenarios (games) we don't want this because garbage-collections often cause stutters. // A common solution to this is object-pooling. // Ceras supports that by allowing you to "catch" unused objects, so you can return them to your object-pool. MyVerySimplePool <Person> pool = new MyVerySimplePool <Person>(); SerializerConfig config = new SerializerConfig(); config.ConfigType <Person>() .ConstructBy(() => new Person()) // select ctor .ConstructBy(pool, () => pool.GetFromPool()); // or create from a pool config.Advanced.DiscardObjectMethod = obj => { pool.ReturnToPool((Person)obj); }; serializer = new CerasSerializer(config); // todo: the example is not fully done yet /* * var personA = new Person { Name = "a", Health = 1 }; * var personB = new Person { Name = "b", Health = 2 }; * var personC = new Person { Name = "c", Health = 3 }; * * personA.BestFriend = personB; * personB.BestFriend = personC; * personC.BestFriend = personA; * * serializer.Serialize(); */ }
static void ReadonlyTest() { // Test #1: // By default the setting is off. Fields are ignored. { SerializerConfig config = new SerializerConfig(); CerasSerializer ceras = new CerasSerializer(config); ReadonlyFieldsTest obj = new ReadonlyFieldsTest(5, "xyz", new ReadonlyFieldsTest.ContainerThingA { Setting1 = 10, Setting2 = "asdasdas" }); var data = ceras.Serialize(obj); var cloneNew = ceras.Deserialize <ReadonlyFieldsTest>(data); Debug.Assert(cloneNew.Int == 1); Debug.Assert(cloneNew.String == "a"); Debug.Assert(cloneNew.Container == null); } // Test #2A: // In the 'Members' mode we expect an exception for readonly value-typed fields. { SerializerConfig config = new SerializerConfig(); config.Advanced.ReadonlyFieldHandling = ReadonlyFieldHandling.Members; config.ConfigType <ReadonlyFieldsTest>() .ConfigMember(f => f.Int).Include() .ConfigMember(f => f.String).Include(); CerasSerializer ceras = new CerasSerializer(config); ReadonlyFieldsTest obj = new ReadonlyFieldsTest(5, "55555", new ReadonlyFieldsTest.ContainerThingA { Setting1 = 555555, Setting2 = "555555555" }); var data = ceras.Serialize(obj); ReadonlyFieldsTest existingTarget = new ReadonlyFieldsTest(6, "66666", null); bool gotException = false; try { var cloneNew = ceras.Deserialize <ReadonlyFieldsTest>(data); } catch (Exception ex) { gotException = true; } Debug.Assert(gotException); // We want an exception } // Test #2B: // In the 'Members' mode (when not dealing with value-types) // we want Ceras to re-use the already existing object { SerializerConfig config = new SerializerConfig(); config.Advanced.ReadonlyFieldHandling = ReadonlyFieldHandling.Members; config.ConfigType <ReadonlyFieldsTest>() .ConfigMember(f => f.Int).Exclude() .ConfigMember(f => f.String).Exclude() .ConfigMember(f => f.Container).Include(ReadonlyFieldHandling.Members); CerasSerializer ceras = new CerasSerializer(config); ReadonlyFieldsTest obj = new ReadonlyFieldsTest(5, "55555", new ReadonlyFieldsTest.ContainerThingA { Setting1 = 555555, Setting2 = "555555555" }); var data = ceras.Serialize(obj); var newContainer = new ReadonlyFieldsTest.ContainerThingA { Setting1 = -1, Setting2 = "this should get overwritten" }; ReadonlyFieldsTest existingTarget = new ReadonlyFieldsTest(6, "66666", newContainer); // populate existing data ceras.Deserialize <ReadonlyFieldsTest>(ref existingTarget, data); // The simple fields should have been ignored Debug.Assert(existingTarget.Int == 6); Debug.Assert(existingTarget.String == "66666"); // The reference itself should not have changed Debug.Assert(existingTarget.Container == newContainer); // The content of the container should be changed now Debug.Assert(newContainer.Setting1 == 555555); Debug.Assert(newContainer.Setting2 == "555555555"); } // Test #3 // In 'ForcedOverwrite' mode Ceras should fix all possible mismatches by force (reflection), // which means that it should work exactly like as if the field were not readonly. { SerializerConfig config = new SerializerConfig(); config.Advanced.ReadonlyFieldHandling = ReadonlyFieldHandling.ForcedOverwrite; CerasSerializer ceras = new CerasSerializer(config); // This time we want Ceras to fix everything, reference mismatches and value mismatches alike. ReadonlyFieldsTest obj = new ReadonlyFieldsTest(5, "55555", new ReadonlyFieldsTest.ContainerThingA { Setting1 = 324, Setting2 = "1134" }); var data = ceras.Serialize(obj); ReadonlyFieldsTest existingTarget = new ReadonlyFieldsTest(123, null, new ReadonlyFieldsTest.ContainerThingB()); // populate existing object ceras.Deserialize <ReadonlyFieldsTest>(ref existingTarget, data); // Now we really check for everything... // Sanity check, no way this could happen, but lets make sure. Debug.Assert(ReferenceEquals(obj, existingTarget) == false); // Fields should be like in the original Debug.Assert(existingTarget.Int == 5); Debug.Assert(existingTarget.String == "55555"); // The container type was wrong, Ceras should have fixed that by instantiating a different object // and writing that into the readonly field. var container = existingTarget.Container as ReadonlyFieldsTest.ContainerThingA; Debug.Assert(container != null); // Contents of the container should be correct as well Debug.Assert(container.Setting1 == 324); Debug.Assert(container.Setting2 == "1134"); } // Test #4: // Everything should work fine when using the MemberConfig attribute as well { var ceras = new CerasSerializer(); var obj = new ReadonlyFieldsTest2(); obj.Numbers.Clear(); obj.Numbers.Add(234); var data = ceras.Serialize(obj); var clone = new ReadonlyFieldsTest2(); var originalList = clone.Numbers; ceras.Deserialize(ref clone, data); Debug.Assert(originalList == clone.Numbers); // actual reference should not have changed Debug.Assert(clone.Numbers.Count == 1); // amount of entries should have changed Debug.Assert(clone.Numbers[0] == 234); // entry itself should be right } // todo: also test the case where the existing object does not match the expected type }
public void Step1_SimpleUsage() { // // 1.) Simple usage // aka. "I'm here for the cool features! I want to optimize for max-performance later" var person = new Person { Name = "riki", Health = 100 }; var ceras = new CerasSerializer(); var data = ceras.Serialize(person); data.VisualizePrint("Simple Person"); var clone1 = ceras.Deserialize <Person>(data); Console.WriteLine($"Clone: Name={clone1.Name}, Health={clone1.Health}"); // 2.) Types // You can also serialize as <object>. // In that case the type information will be included. // If a type is written it will only be written ONCE, so a List<Person> will not suddenly waste a // ton of space by continously writing the type-names var objectData = ceras.Serialize <object>(person); objectData.VisualizePrint("Person as <object>"); var objectClone = ceras.Deserialize <object>(objectData); // // 3.) Improvement: // Recycle the serialization buffer by keeping the reference to it around. // Optionally we can even let Ceras create (or resize) the buffer for us. byte[] buffer = null; int writtenBytes = ceras.Serialize(person, ref buffer); // Now we could send this over the network, for example: // socket.Send(buffer, writtenBytes, SocketFlags.None); var clone2 = ceras.Deserialize <Person>(buffer); // // 4.) // Deciding what gets serialized // There are multiple ways to configure what members to serialize // Ceras determines member inclusion in this order: // // - a. Any custom configuration using ConfigType<T> or ConfigType(type) // // - b. [Include] and [Exclude] attributes on individual members // // - c. [MemberConfig] attribute // // - d. "DefaultTargets" setting in the SerializerConfig // which defaults to 'TargetMember.PublicFields' // SerializerConfig config = new SerializerConfig(); config.DefaultTargets = TargetMember.PublicProperties | TargetMember.PrivateFields; config.ConfigType <Person>() .ConfigMember(p => p.Name).Include() .ConfigMember(p => p.BestFriend).Include(); // // 5.) Circular references // Serializers commonly have trouble serializing circular references. // Ceras supports every possible object-graph, and there's literally // nothing to do or configure, it just works out of the box. // Lets make an example anyway... var personA = new Person { Name = "alice" }; var personB = new Person { Name = "bob" }; personA.BestFriend = personB; personB.BestFriend = personA; var dataWithCircularReferences = ceras.Serialize(personA); dataWithCircularReferences.VisualizePrint("Circular references data"); var cloneA = ceras.Deserialize <Person>(dataWithCircularReferences); if (cloneA.BestFriend.BestFriend.BestFriend.BestFriend.BestFriend.BestFriend == cloneA) { Console.WriteLine("Circular reference serialization working as intended!"); } else { throw new Exception("There was some problem!"); } // Works with self-references! // Ceras maintains object references while deserializing, even if the object a field/prop points to doesn't exist yet var personC = new Person { Name = "abc" }; personC.BestFriend = personC; var cloneC = ceras.Deserialize <Person>(ceras.Serialize(personC)); Debug.Assert(cloneC.BestFriend.BestFriend.BestFriend.BestFriend == cloneC); // Fully maintains identity of objects! // There is only one actual object instance here (personC). The array refers to the same object two times. // While Ceras deserializes the array it only creates a single instance of 'Person', exactly as intended. Person[] personArray = new Person[2]; personArray[0] = personC; personArray[1] = personC; var personArrayClone = ceras.Deserialize <Person[]>(ceras.Serialize(personArray)); Debug.Assert(personArray[0] == personArray[1]); Debug.Assert(ReferenceEquals(personArray[0], personArray[1])); Debug.Assert(personArray[0].BestFriend == personArray[1].BestFriend); Debug.Assert(personArray[0].BestFriend.BestFriend == personArray[1]); }
static void ExpressionTreesTest() { // Primitive test (private readonly in a base type) { SerializerConfig config = new SerializerConfig(); config.ConfigType <ReadonlyTestClass>() .ConstructByUninitialized() .SetReadonlyHandling(ReadonlyFieldHandling.ForcedOverwrite) .SetTargetMembers(TargetMember.PrivateFields); var ceras = new CerasSerializer(config); var obj = new ReadonlyTestClass("a"); var data = ceras.Serialize(obj); var clone = ceras.Deserialize <ReadonlyTestClass>(data); Debug.Assert(obj.GetName() == clone.GetName()); Debug.Assert(obj.GetBaseName() == clone.GetBaseName()); Console.WriteLine(); } // Small test 1 { Expression <Func <string, int, char> > getCharAtIndex = (text, index) => text.ElementAt(index); MethodCallExpression body = (MethodCallExpression)getCharAtIndex.Body; // Serialize and deserialize delegate SerializerConfig config = new SerializerConfig(); var ceras = new CerasSerializer(config); var data = ceras.Serialize <object>(body); var dataAsStr = Encoding.ASCII.GetString(data).Replace('\0', ' '); var clonedExp = (MethodCallExpression)ceras.Deserialize <object>(data); Debug.Assert(clonedExp.Method == body.Method); Debug.Assert(clonedExp.Arguments.Count == body.Arguments.Count); } // Small test 2 { // Test data string inputString = "abcdefgh"; Expression <Func <string, int, char> > getCharAtIndex = (text, index) => (text.ElementAt(index).ToString() + text[index])[0]; var del1 = getCharAtIndex.Compile(); char c1 = del1(inputString, 2); // Serialize and deserialize expression SerializerConfig config = new SerializerConfig(); var ceras = new CerasSerializer(config); var data = ceras.Serialize(getCharAtIndex); var dataAsStr = Encoding.ASCII.GetString(data).Replace('\0', ' '); var clonedExp = ceras.Deserialize <Expression <Func <string, int, char> > >(data); // Compile the restored expression, check if it works and returns the same result var del2 = clonedExp.Compile(); // Check single case var c2 = del2(inputString, 2); Debug.Assert(c1 == c2); // Check all cases for (int i = 0; i < inputString.Length; i++) { Debug.Assert(del1(inputString, i) == del2(inputString, i)); } } }