private static void AssertCanGenerateCorrectMemberSetter( object source, IFastSerialisationTypeConverter[] typeConverters, int expectedNumberOfMemberSettersGenerated, int expectedNumberOfMemberSettersThatCanNotBeGenerated) { // TryToGenerateMemberSetters will try to return member setters for each type that it encountered while analysing the source type - for example, if // source is an instance of "PersonDetails" and if "PersonDetails" has an int Key property and a "NameDetails" Name property where "NameDetails" is // a class with a single string property "Name" then TryToGenerateMemberSetters will try to generate member setters for both the "PersonDetails" // and "NameDetails" classes (it may not succeed, in which case it may return zero or one member setters, or it may succeed completely and return // two member setters). It won't return member setters for values that have first class IWrite support (primitives, strings, DateTime, etc..) var sourceType = source.GetType(); var deepMemberSetterCache = new ConcurrentDictionary <Type, DeepCompiledMemberSettersGenerationResults>(); var memberSetterDetailsForAllTypesInvolved = GetMemberSettersFor(sourceType, typeConverters, deepMemberSetterCache); Assert.NotNull(memberSetterDetailsForAllTypesInvolved); // We should always get a non-null reference for this (but doesn't hurt to confirm) // Try to get member setter for the source type if (!memberSetterDetailsForAllTypesInvolved.MemberSetters.TryGetValue(sourceType, out var memberSetterDetailsForType)) { memberSetterDetailsForType = null; } Assert.NotNull(memberSetterDetailsForType); // We should know how many member setters we expected to be generated (and how many it's not possible to generate), so let's confirm Assert.Equal(expectedNumberOfMemberSettersGenerated, memberSetterDetailsForAllTypesInvolved.MemberSetters.Count(kvp => kvp.Value != null)); Assert.Equal(expectedNumberOfMemberSettersThatCanNotBeGenerated, memberSetterDetailsForAllTypesInvolved.MemberSetters.Count(kvp => kvp.Value == null)); byte[] serialised; using (var stream = new MemoryStream()) { // See notes in SimpleMemberSetterCompilationTests's "AssertCanGenerateCorrectMemberSetter" method - this code is relying more on implementation // details that I would like but it seems like the least of all evils to do so foreach (var typeName in memberSetterDetailsForAllTypesInvolved.TypeNamesToDeclare) { var typeNameBytes = new[] { (byte)BinarySerialisationDataType.FieldNamePreLoad }.Concat(typeName.AsStringAndReferenceID).ToArray(); stream.Write(typeNameBytes, 0, typeNameBytes.Length); } foreach (var fieldName in memberSetterDetailsForAllTypesInvolved.FieldNamesToDeclare) { var fieldNameBytes = new[] { (byte)BinarySerialisationDataType.FieldNamePreLoad }.Concat(fieldName.AsStringAndReferenceID).ToArray(); stream.Write(fieldNameBytes, 0, fieldNameBytes.Length); } var writer = new BinarySerialisationWriter(stream); writer.ObjectStart(source.GetType()); memberSetterDetailsForType(source, writer); writer.ObjectEnd(); serialised = stream.ToArray(); } var clone = BinarySerialisation.Deserialise <object>(serialised, typeConverters.OfType <IDeserialisationTypeConverter>().ToArray()); if (!ObjectComparer.AreEqual(source, clone, out var differenceSummaryIfNotEqual)) { throw new Exception("Clone failed: " + differenceSummaryIfNotEqual); } }
public void EnsureThatMemberSetterPreparedForElementTypeWhenTargetTypeIsArray() { using (var stream = new MemoryStream()) { var writer = new BinarySerialisationWriter( stream, ReferenceReuseOptions.SpeedyButLimited, DefaultTypeAnalyser.Instance, new ConcurrentDictionary <Type, BinarySerialisationDeepCompiledMemberSetters.DeepCompiledMemberSettersGenerationResults>() ); var generatedMemberSetters = writer.PrepareForSerialisation(typeof(SealedClassWithSingleStringProperty[]), new IFastSerialisationTypeConverter[0]); Assert.NotNull(generatedMemberSetters); // Should never get a null response back Assert.Single(generatedMemberSetters); // In this case, there should be a single entry that is a non-null member setter for SealedClassWithSingleStringProperty (NOT array of) Assert.Equal(typeof(SealedClassWithSingleStringProperty), generatedMemberSetters.First().Key); Assert.NotNull(generatedMemberSetters.First().Value); } }
private static void AssertCanGenerateCorrectMemberSetter(object source) { var memberSetterDetails = TryToGenerateMemberSetter(source.GetType()); Assert.NotNull(memberSetterDetails); byte[] serialised; using (var stream = new MemoryStream()) { // In an ideal world, this wouldn't be so tied to implementation details of the Serialiser and BinarySerialisationWriter classes but I can't // immediately think of a way to fully test this otherwise - one option would be to skip the writing of the FieldNamePreLoad data and to skip // the ObjectStart and ObjectEnd calls and to then read the data back out via a BinarySerialisationReader and manually compare the field and // property names to expected values but I wanted to offload that sort of comparison work to a library like CompareNetObjects! // - "Optimisied member setters" are not usually generated until after an instance of a type has been serialised without, in which case all // field names will have appeared in the serialised data at least once, which is important because the member setters will write out Name // Reference IDs for fields and the reader needs to know what strings those IDs map on to. Since this code won't be serialising an instance // of each type before using the member setter, FieldNamePreLoad data can be injected into the start of the serialised data and then the // reader will be able to refer to use that to map IDs to strings. foreach (var fieldName in memberSetterDetails.FieldsSet) { var fieldNameBytes = new[] { (byte)BinarySerialisationDataType.FieldNamePreLoad }.Concat(fieldName.AsStringAndReferenceID).ToArray(); stream.Write(fieldNameBytes, 0, fieldNameBytes.Length); } var writer = new BinarySerialisationWriter(stream); writer.ObjectStart(source.GetType()); memberSetterDetails.GetCompiledMemberSetter()(source, writer); writer.ObjectEnd(); serialised = stream.ToArray(); } var clone = BinarySerialisation.Deserialise <object>(serialised); if (!ObjectComparer.AreEqual(source, clone, out var differenceSummaryIfNotEqual)) { throw new Exception("Clone failed: " + differenceSummaryIfNotEqual); } }
public static void DeprecatedPropertyIsWrittenAgainstOldPropertyNameAsWellAsNew() { // Define two versions of the same class where the V1 looks like this: // // public class SomethingWithName // { // public string NameOld { get; set; } // } // // .. and the V2 looks like this: // // public class SomethingWithName // { // public string NameNew { get; set; } // // [Deprecated] // public string NameOld { get { return NameNew; } } // } // // If an instance of V2 entity is serialised and then deserialised into the V1 entity type then the "NameOld" field of the V1 entity instance should be populated (in order for // this to work, the member-setter-generation logic needs to consider fields and properties and the attributes that they do or do not have) var entityTypeV1 = ConstructType( GetModuleBuilder("DynamicAssemblyFor" + GetMyName(), new Version(1, 0)), "SomethingWithName", fields: new Tuple <string, Type> [0], optionalFinisher: typeBuilder => { // Backing field for "NameOld" var nameOldfieldBuilder = typeBuilder.DefineField(BackingFieldHelpers.GetBackingFieldName("NameOld"), typeof(string), FieldAttributes.Private); // Property for "NameOld" var nameOldPropertyBuilder = typeBuilder.DefineProperty("NameOld", PropertyAttributes.None, typeof(string), parameterTypes: Type.EmptyTypes); var nameOldGetterBuilder = typeBuilder.DefineMethod("get_NameOld", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(string), parameterTypes: Type.EmptyTypes); var ilGeneratorForNameOldGetter = nameOldGetterBuilder.GetILGenerator(); ilGeneratorForNameOldGetter.Emit(OpCodes.Ldarg_0); ilGeneratorForNameOldGetter.Emit(OpCodes.Ldfld, nameOldfieldBuilder); ilGeneratorForNameOldGetter.Emit(OpCodes.Ret); nameOldPropertyBuilder.SetGetMethod(nameOldGetterBuilder); var nameOldSetterBuilder = typeBuilder.DefineMethod("set_NameOld", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(void), parameterTypes: new[] { typeof(string) }); var ilGeneratorForNameOldSetter = nameOldSetterBuilder.GetILGenerator(); ilGeneratorForNameOldSetter.Emit(OpCodes.Ldarg_0); ilGeneratorForNameOldSetter.Emit(OpCodes.Ldarg_1); ilGeneratorForNameOldSetter.Emit(OpCodes.Stfld, nameOldfieldBuilder); ilGeneratorForNameOldSetter.Emit(OpCodes.Ret); nameOldPropertyBuilder.SetSetMethod(nameOldSetterBuilder); } ); var entityTypeV2 = ConstructType( GetModuleBuilder("DynamicAssemblyFor" + GetMyName(), new Version(1, 0)), "SomethingWithName", fields: new Tuple <string, Type> [0], optionalFinisher: typeBuilder => { // Backing field for "NameNew" var nameNewfieldBuilder = typeBuilder.DefineField(BackingFieldHelpers.GetBackingFieldName("NameNew"), typeof(string), FieldAttributes.Private); // Property for "NameNew" var nameNewPropertyBuilder = typeBuilder.DefineProperty("NameNew", PropertyAttributes.None, typeof(string), parameterTypes: Type.EmptyTypes); var nameNewGetterBuilder = typeBuilder.DefineMethod("get_NameNew", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(string), parameterTypes: Type.EmptyTypes); var ilGeneratorForNameNewGetter = nameNewGetterBuilder.GetILGenerator(); ilGeneratorForNameNewGetter.Emit(OpCodes.Ldarg_0); ilGeneratorForNameNewGetter.Emit(OpCodes.Ldfld, nameNewfieldBuilder); ilGeneratorForNameNewGetter.Emit(OpCodes.Ret); nameNewPropertyBuilder.SetGetMethod(nameNewGetterBuilder); var nameNewSetterBuilder = typeBuilder.DefineMethod("set_NameNew", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(void), parameterTypes: new[] { typeof(string) }); var ilGeneratorForNameNewSetter = nameNewSetterBuilder.GetILGenerator(); ilGeneratorForNameNewSetter.Emit(OpCodes.Ldarg_0); ilGeneratorForNameNewSetter.Emit(OpCodes.Ldarg_1); ilGeneratorForNameNewSetter.Emit(OpCodes.Stfld, nameNewfieldBuilder); ilGeneratorForNameNewSetter.Emit(OpCodes.Ret); nameNewPropertyBuilder.SetSetMethod(nameNewSetterBuilder); // Property for "NameOld" that has [Deprecated] attribute and whose getter access "NameNew" var nameOldPropertyBuilder = typeBuilder.DefineProperty("NameOld", PropertyAttributes.None, typeof(string), parameterTypes: Type.EmptyTypes); var nameOldGetterBuilder = typeBuilder.DefineMethod("get_NameOld", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(string), parameterTypes: Type.EmptyTypes); var ilGeneratorForNameOldGetter = nameOldGetterBuilder.GetILGenerator(); ilGeneratorForNameOldGetter.Emit(OpCodes.Ldarg_0); ilGeneratorForNameOldGetter.Emit(OpCodes.Call, nameNewGetterBuilder); ilGeneratorForNameOldGetter.Emit(OpCodes.Ret); nameOldPropertyBuilder.SetGetMethod(nameOldGetterBuilder); nameOldPropertyBuilder.SetCustomAttribute(new CustomAttributeBuilder(typeof(DeprecatedAttribute).GetConstructor(new[] { typeof(string) }), new object[] { null })); } ); // Create an instance of the V2 entity var source = Activator.CreateInstance(entityTypeV2); entityTypeV2.GetProperty("NameNew").SetValue(source, "abc"); // Try to create a member setter for it - this should work since it only has string fields and properties var memberSetterDetails = GetMemberSetterAvailability( entityTypeV2, DefaultTypeAnalyser.Instance, valueWriterRetriever: t => null // No complex nested member setter madness required, so provide a valueWriterRetriever delegate that always returns null ) .MemberSetterDetailsIfSuccessful; Assert.NotNull(memberSetterDetails); // Serialise this v2 entity instance byte[] serialised; using (var stream = new MemoryStream()) { foreach (var fieldName in memberSetterDetails.FieldsSet) { var fieldNameBytes = new[] { (byte)BinarySerialisationDataType.FieldNamePreLoad }.Concat(fieldName.AsStringAndReferenceID).ToArray(); stream.Write(fieldNameBytes, 0, fieldNameBytes.Length); } var writer = new BinarySerialisationWriter(stream); writer.ObjectStart(source.GetType()); memberSetterDetails.GetCompiledMemberSetter()(source, writer); writer.ObjectEnd(); serialised = stream.ToArray(); } // Ensure that it works deserialising back to an older version of the type var deserialisedAsV1 = ResolveDynamicAssembliesWhilePerformingAction( () => BinarySerialisation.Deserialise <object>(serialised), entityTypeV1 ); Assert.NotNull(deserialisedAsV1); Assert.IsType(entityTypeV1, deserialisedAsV1); Assert.Equal("abc", deserialisedAsV1.GetType().GetProperty("NameOld").GetValue(deserialisedAsV1)); }