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);
     }
 }
Пример #3
0
        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));
        }