public FieldInfo[] GetAllFieldsThatShouldBeSet(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }

            var fields      = new List <FieldInfo>();
            var currentType = type;

            while (currentType != null)
            {
                foreach (var field in currentType.GetFields(BinaryReaderWriterShared.MemberRetrievalBindingFlags))
                {
                    if (!BinaryReaderWriterShared.IgnoreField(field) &&
                        (field.GetCustomAttribute <OptionalWhenDeserialisingAttribute>() == null) &&
                        (BackingFieldHelpers.TryToGetPropertyRelatingToBackingField(field)?.GetCustomAttribute <OptionalWhenDeserialisingAttribute>() == null))
                    {
                        fields.Add(field);
                    }
                }
                currentType = currentType.BaseType;
            }
            return(fields.ToArray());
        }
        public static void AllowDeserialisationIfFieldCanNotBeSetIfFieldIsForAutoPropertyThatIsMarkedAsOptionalForDeserialisation()
        {
            var sourceType     = ConstructType(GetModuleBuilder("DynamicAssemblyFor" + GetMyName(), new Version(1, 0)), "MyClass", new Tuple <string, Type> [0]);
            var instance       = Activator.CreateInstance(sourceType);
            var serialisedData = BinarySerialisation.Serialise(instance);

            const string namePropertyName = "Name";
            var          destinationType  = ConstructType(
                GetModuleBuilder("DynamicAssemblyFor" + GetMyName(), new Version(1, 0)),
                "MyClass",
                fields: new Tuple <string, Type> [0],
                optionalFinisher: typeBuilder =>
            {
                // Need to define the field using this lambda rather than specifying it through the fields argument because we need the reference for use in the property getter
                var fieldBuilder    = typeBuilder.DefineField(BackingFieldHelpers.GetBackingFieldName(namePropertyName), typeof(string), FieldAttributes.Private);
                var propertyBuilder = typeBuilder.DefineProperty(namePropertyName, PropertyAttributes.None, typeof(string), parameterTypes: Type.EmptyTypes);
                propertyBuilder.SetCustomAttribute(new CustomAttributeBuilder(typeof(DeprecatedAttribute).GetConstructor(new[] { typeof(string) }), new object[] { null }));
                var getterBuilder = typeBuilder.DefineMethod("get_" + namePropertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(string), parameterTypes: Type.EmptyTypes);
                var ilGenerator   = getterBuilder.GetILGenerator();
                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Ldfld, fieldBuilder);
                ilGenerator.Emit(OpCodes.Ret);
                propertyBuilder.SetGetMethod(getterBuilder);
                propertyBuilder.SetCustomAttribute(new CustomAttributeBuilder(typeof(OptionalWhenDeserialisingAttribute).GetConstructor(Type.EmptyTypes), new object[0]));
            }
                );
            var clone = ResolveDynamicAssembliesWhilePerformingAction(
                () => Deserialise(serialisedData, destinationType),
                destinationType
                );
            var namePropertyOnDestination = destinationType.GetProperty(namePropertyName);

            Assert.Null(namePropertyOnDestination.GetValue(clone));
        }
        public static void DeprecatedPropertyShouldBeSerialisedAsIfItIsAnAutoProperty()
        {
            // In a real world scenario, the "future type" would have other properties and the [Deprecated] one(s) would have values computed from them.. for the interests of this
            // unit test, the future type (typeWithDeprecatedProperty) will ONLY have a [Deprecated] property that has a "computed value" (ie. a getter that always returns 123)
            const string idPropertyName = "Id";
            const int    hardCodedIdValueForDeprecatedProperty = 123;
            var          typeWithDeprecatedProperty            = ConstructType(
                GetModuleBuilder("DynamicAssemblyFor" + GetMyName(), new Version(1, 0)),
                "MyClass",
                fields: new Tuple <string, Type> [0],
                optionalFinisher: typeBuilder =>
            {
                var propertyBuilder = typeBuilder.DefineProperty(idPropertyName, PropertyAttributes.None, typeof(int), parameterTypes: Type.EmptyTypes);
                propertyBuilder.SetCustomAttribute(new CustomAttributeBuilder(typeof(DeprecatedAttribute).GetConstructor(new[] { typeof(string) }), new object[] { null }));
                var getterBuilder = typeBuilder.DefineMethod("get_" + idPropertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(int), parameterTypes: Type.EmptyTypes);
                var ilGenerator   = getterBuilder.GetILGenerator();
                ilGenerator.Emit(OpCodes.Ldc_I4, hardCodedIdValueForDeprecatedProperty);
                ilGenerator.Emit(OpCodes.Ret);
                propertyBuilder.SetGetMethod(getterBuilder);
            }
                );
            var instance       = Activator.CreateInstance(typeWithDeprecatedProperty);
            var serialisedData = BinarySerialisation.Serialise(instance);

            // For the sake of this test, the type that will be deserialised to only needs to have a backing field for an auto property (the field that we're expecting to
            // get set) and so that's all that is being configured. It would be closer to a real use case if there was a property with a getter that used this backing field
            // but it wouldn't be used by this test and so it doesn't need to be created.
            var idBackingFieldName = BackingFieldHelpers.GetBackingFieldName(idPropertyName);
            var typeThatStillHasThePropertyOnIt = ConstructType(
                GetModuleBuilder("DynamicAssemblyFor" + GetMyName(), new Version(1, 0)),
                "MyClass",
                new[] { Tuple.Create(idBackingFieldName, typeof(int)) }
                );
            var deserialised = ResolveDynamicAssembliesWhilePerformingAction(
                () => Deserialise(serialisedData, typeThatStillHasThePropertyOnIt),
                typeThatStillHasThePropertyOnIt
                );
            var idBackingFieldOnDestination = typeThatStillHasThePropertyOnIt.GetField(idBackingFieldName);

            Assert.Equal(123, idBackingFieldOnDestination.GetValue(deserialised));
        }
        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));
        }
        /// <summary>
        /// There may be cases where a value is deserialised for a field that does not exist on the destination type - this could happen if data from an old version of the type is
        /// being deserialised into a new version of the type, in which case we need to check for any [Deprecated(replacedBy: ..)] properties that exist to provide a way to take
        /// this value and set the [Deprecated] property/ies using the field that no longer exists. Note that it's also possible that the serialised data contains a field that
        /// does not exist on the destination type because the serialised data is from a newer version of the type and the destination is an older version of that type that
        /// has never had the field - in that case, this method wil return null.
        /// </summary>
        public DeprecatedPropertySettingDetails TryToGetPropertySettersAndFieldsToConsiderToHaveBeenSet(Type typeToLookForPropertyOn, string fieldName, string typeNameIfRequired, Type fieldValueTypeIfAvailable)
        {
            if (typeToLookForPropertyOn == null)
            {
                throw new ArgumentNullException(nameof(typeToLookForPropertyOn));
            }
            if (string.IsNullOrWhiteSpace(fieldName))
            {
                throw new ArgumentException($"Null/blank {nameof(fieldName)} specified");
            }

            var propertySetters = new List <Tuple <PropertyInfo, MemberUpdater> >();
            var fieldsToConsiderToHaveBeenSetViaDeprecatedProperties = new List <FieldInfo>();
            var propertyName = BackingFieldHelpers.TryToGetNameOfPropertyRelatingToBackingField(fieldName) ?? fieldName;

            while (typeToLookForPropertyOn != null)
            {
                if ((typeNameIfRequired == null) || (typeToLookForPropertyOn.AssemblyQualifiedName == typeNameIfRequired))
                {
                    // TODO: Not sure about this fieldValueTypeIfAvailable business!
                    var deprecatedProperty = typeToLookForPropertyOn.GetProperties(BinaryReaderWriterShared.MemberRetrievalBindingFlags)
                                             .Where(p =>
                                                    (p.Name == propertyName) &&
                                                    (p.DeclaringType == typeToLookForPropertyOn) &&
                                                    (p.GetIndexParameters().Length == 0) &&
                                                    (((fieldValueTypeIfAvailable == null) && !p.PropertyType.IsValueType) || ((fieldValueTypeIfAvailable != null) && p.PropertyType.IsAssignableFrom(fieldValueTypeIfAvailable)))
                                                    )
                                             .Select(p => new { Property = p, ReplaceBy = p.GetCustomAttribute <DeprecatedAttribute>()?.ReplacedBy })
                                             .FirstOrDefault(p => p.ReplaceBy != null);    // Safe to use FirstOrDefault because there can't be multiple [Deprecated] as AllowMultiple is not set to true on the attribute class
                    if (deprecatedProperty != null)
                    {
                        // Try to find a field that the "ReplacedBy" value relates to (if we find it then we'll consider it to have been set because setting the
                        // deprecated property should set it))
                        propertySetters.Add(Tuple.Create(deprecatedProperty.Property, GetPropertyWriter(deprecatedProperty.Property)));
                        var field = typeToLookForPropertyOn.GetFields(BinaryReaderWriterShared.MemberRetrievalBindingFlags)
                                    .Where(f => (f.Name == deprecatedProperty.ReplaceBy) && (f.DeclaringType == typeToLookForPropertyOn))
                                    .FirstOrDefault();
                        if (field == null)
                        {
                            // If the "ReplacedBy" value didn't directly match a field then try to find a property that it matches and then see if there is a
                            // backing field for that property that we can set (if we find this then we'll consider to have been set because setting the deprecated
                            // property should set it)
                            var property = typeToLookForPropertyOn.GetProperties(BinaryReaderWriterShared.MemberRetrievalBindingFlags)
                                           .Where(p => (p.Name == deprecatedProperty.ReplaceBy) && (p.DeclaringType == typeToLookForPropertyOn) && (p.GetIndexParameters().Length == 0))
                                           .FirstOrDefault();
                            if (property != null)
                            {
                                var nameOfPotentialBackingFieldForProperty = BackingFieldHelpers.GetBackingFieldName(property.Name);
                                field = typeToLookForPropertyOn.GetFields(BinaryReaderWriterShared.MemberRetrievalBindingFlags)
                                        .Where(f => (f.Name == nameOfPotentialBackingFieldForProperty) && (f.DeclaringType == typeToLookForPropertyOn))
                                        .FirstOrDefault();
                            }
                        }
                        if (field != null)
                        {
                            // Although the field hasn't directly been set, it should have been set indirectly by setting the property value above (unless the [Deprecated]
                            // "ReplaceBy" value was lying)
                            fieldsToConsiderToHaveBeenSetViaDeprecatedProperties.Add(field);
                        }
                    }
                }
                typeToLookForPropertyOn = typeToLookForPropertyOn.BaseType;
            }
            if (!propertySetters.Any())
            {
                return(null);
            }

            Type compatibleCommonPropertyTypeIfKnown = null;

            foreach (var propertySetter in propertySetters)
            {
                if (compatibleCommonPropertyTypeIfKnown == null)
                {
                    compatibleCommonPropertyTypeIfKnown = propertySetter.Item1.PropertyType;
                }
                else if (propertySetter.Item1.PropertyType != compatibleCommonPropertyTypeIfKnown)
                {
                    if (compatibleCommonPropertyTypeIfKnown.IsAssignableFrom(propertySetter.Item1.PropertyType))
                    {
                        // If this property is of a more specific type than propertyTypeIfKnown but the current propertyTypeIfKnown could be satisfied by it then record
                        // this type going forward (any other properties will need to either be this type of be assignable to it otherwise we won't be able to deserialise
                        // a single value that can be used to populate all of the related properties - only for cases where there are multiple, obviously)
                        compatibleCommonPropertyTypeIfKnown = propertySetter.Item1.PropertyType;
                    }
                    else if (!propertySetter.Item1.PropertyType.IsAssignableFrom(compatibleCommonPropertyTypeIfKnown))
                    {
                        // If propertySetter.PropertyType is not the same as propertyTypeIfKnown and if propertyTypeIfKnown is not assignable from propertySetter.PropertyType
                        // and propertySetter.PropertyType is not assigned from propertyTypeIfKnown then we've come to an impossible situation - if there are multiple properties
                        // then there must be a single type that a value may be deserialised as that may be used to set ALL of the properties. Since there isn't, we have to throw.
                        throw new InvalidSerialisationDataFormatException($"Type {typeToLookForPropertyOn.Name} has [Deprecated] properties that are all set by data for field {fieldName} but which have incompatible types");
                    }
                }
            }
            return(new DeprecatedPropertySettingDetails(
                       compatibleCommonPropertyTypeIfKnown,
                       propertySetters.Select(p => p.Item2).ToArray(),
                       fieldsToConsiderToHaveBeenSetViaDeprecatedProperties.ToArray()
                       ));
        }