예제 #1
0
 private void RaiseAndSetIfChanged(MethodReference raiseAndSetIfChangedMethod, TypeDefinition targetType, FieldDefinition field, ILProcessor il, string propertyName)
 {
     il.Emit(OpCodes.Ldarg_0);                                  // this
     il.Emit(OpCodes.Ldarg_0);                                  // this
     il.Emit(OpCodes.Ldflda, field.BindDefinition(targetType)); // pop -> ref this.$PropertyName
     il.Emit(OpCodes.Ldarg_1);                                  // value
     il.Emit(OpCodes.Ldstr, propertyName);                      // "PropertyName"
     il.Emit(OpCodes.Call, raiseAndSetIfChangedMethod);         // pop * 4 -> this.RaiseAndSetIfChanged(this.$PropertyName, value, "PropertyName")
     il.Emit(OpCodes.Pop);                                      // We don't care about the result of RaiseAndSetIfChanged, so pop it off the stack (stack is now empty)
     il.Emit(OpCodes.Ret);                                      // Return out of the function
 }
예제 #2
0
    /// <summary>
    /// Executes this property weaver.
    /// </summary>
    public void Execute()
    {
        if (ModuleDefinition is null)
        {
            LogInfo?.Invoke("The module definition has not been defined.");
            return;
        }

        var reactiveUI = ModuleDefinition.AssemblyReferences.Where(x => x.Name == "ReactiveUI").OrderByDescending(x => x.Version).FirstOrDefault();

        if (reactiveUI is null)
        {
            LogInfo?.Invoke("Could not find assembly: ReactiveUI (" + string.Join(", ", ModuleDefinition.AssemblyReferences.Select(x => x.Name)) + ")");
            return;
        }

        LogInfo?.Invoke($"{reactiveUI.Name} {reactiveUI.Version}");
        var helpers = ModuleDefinition.AssemblyReferences.Where(x => x.Name == "ReactiveUI.Fody.Helpers").OrderByDescending(x => x.Version).FirstOrDefault();

        if (helpers is null)
        {
            LogInfo?.Invoke("Could not find assembly: ReactiveUI.Fody.Helpers (" + string.Join(", ", ModuleDefinition.AssemblyReferences.Select(x => x.Name)) + ")");
            return;
        }

        LogInfo?.Invoke($"{helpers.Name} {helpers.Version}");

        var exceptionName = typeof(Exception).FullName;

        if (exceptionName is null)
        {
            LogInfo?.Invoke("Could not find the full name for System.Exception");
            return;
        }

        var reactiveObject = ModuleDefinition.FindType("ReactiveUI", "ReactiveObject", reactiveUI);

        // The types we will scan are subclasses of ReactiveObject
        var targetTypes = ModuleDefinition.GetAllTypes().Where(x => x.BaseType is not null && reactiveObject.IsAssignableFrom(x.BaseType));

        var observableAsPropertyHelper         = ModuleDefinition.FindType("ReactiveUI", "ObservableAsPropertyHelper`1", reactiveUI, "T");
        var observableAsPropertyAttribute      = ModuleDefinition.FindType("ReactiveUI.Fody.Helpers", "ObservableAsPropertyAttribute", helpers);
        var observableAsPropertyHelperGetValue = ModuleDefinition.ImportReference(observableAsPropertyHelper.Resolve().Properties.Single(x => x.Name == "Value").GetMethod);
        var exceptionDefinition   = FindType?.Invoke(exceptionName);
        var constructorDefinition = exceptionDefinition.GetConstructors().Single(x => x.Parameters.Count == 1);
        var exceptionConstructor  = ModuleDefinition.ImportReference(constructorDefinition);

        foreach (var targetType in targetTypes)
        {
            foreach (var property in targetType.Properties.Where(x => x.IsDefined(observableAsPropertyAttribute) || (x.GetMethod?.IsDefined(observableAsPropertyAttribute) ?? false)).ToArray())
            {
                var genericObservableAsPropertyHelper         = observableAsPropertyHelper.MakeGenericInstanceType(property.PropertyType);
                var genericObservableAsPropertyHelperGetValue = observableAsPropertyHelperGetValue.Bind(genericObservableAsPropertyHelper);
                ModuleDefinition.ImportReference(genericObservableAsPropertyHelperGetValue);

                // Declare a field to store the property value
                var field = new FieldDefinition("$" + property.Name, FieldAttributes.Private, genericObservableAsPropertyHelper);
                targetType.Fields.Add(field);

                // It's an auto-property, so remove the generated field
                if (property.SetMethod is not null && property.SetMethod.HasBody)
                {
                    // Remove old field (the generated backing field for the auto property)
                    var oldField           = (FieldReference)property.GetMethod.Body.Instructions.Single(x => x.Operand is FieldReference).Operand;
                    var oldFieldDefinition = oldField.Resolve();
                    targetType.Fields.Remove(oldFieldDefinition);

                    // Re-implement setter to throw an exception
                    property.SetMethod.Body = new MethodBody(property.SetMethod);
                    property.SetMethod.Body.Emit(il =>
                    {
                        il.Emit(OpCodes.Ldstr, "Never call the setter of an ObservableAsPropertyHelper property.");
                        il.Emit(OpCodes.Newobj, exceptionConstructor);
                        il.Emit(OpCodes.Throw);
                        il.Emit(OpCodes.Ret);
                    });
                }

                property.GetMethod.Body = new MethodBody(property.GetMethod);
                property.GetMethod.Body.Emit(il =>
                {
                    var isValid = il.Create(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);                                               // this
                    il.Emit(OpCodes.Ldfld, field.BindDefinition(targetType));               // pop -> this.$PropertyName
                    il.Emit(OpCodes.Dup);                                                   // Put an extra copy of this.$PropertyName onto the stack
                    il.Emit(OpCodes.Brtrue, isValid);                                       // If the helper is null, return the default value for the property
                    il.Emit(OpCodes.Pop);                                                   // Drop this.$PropertyName
                    EmitDefaultValue(property.GetMethod.Body, il, property.PropertyType);   // Put the default value onto the stack
                    il.Emit(OpCodes.Ret);                                                   // Return that default value
                    il.Append(isValid);                                                     // Add a marker for if the helper is not null
                    il.Emit(OpCodes.Callvirt, genericObservableAsPropertyHelperGetValue);   // pop -> this.$PropertyName.Value
                    il.Emit(OpCodes.Ret);                                                   // Return the value that is on the stack
                });
            }
        }
    }
        public void Execute()
        {
            var reactiveUI = ModuleDefinition.AssemblyReferences.Where(x => x.Name == "ReactiveUI").OrderByDescending(x => x.Version).FirstOrDefault();

            if (reactiveUI == null)
            {
                LogInfo("Could not find assembly: ReactiveUI (" + string.Join(", ", ModuleDefinition.AssemblyReferences.Select(x => x.Name)) + ")");
                return;
            }
            LogInfo($"{reactiveUI.Name} {reactiveUI.Version}");
            var helpers = ModuleDefinition.AssemblyReferences.Where(x => x.Name == "ReactiveUI.Fody.Helpers").OrderByDescending(x => x.Version).FirstOrDefault();

            if (helpers == null)
            {
                LogInfo("Could not find assembly: ReactiveUI.Fody.Helpers (" + string.Join(", ", ModuleDefinition.AssemblyReferences.Select(x => x.Name)) + ")");
                return;
            }
            LogInfo($"{helpers.Name} {helpers.Version}");
            var reactiveObject           = new TypeReference("ReactiveUI", "IReactiveObject", ModuleDefinition, reactiveUI);
            var targetTypes              = ModuleDefinition.GetAllTypes().Where(x => x.BaseType != null && reactiveObject.IsAssignableFrom(x.BaseType)).ToArray();
            var reactiveObjectExtensions = new TypeReference("ReactiveUI", "IReactiveObjectExtensions", ModuleDefinition, reactiveUI).Resolve();

            if (reactiveObjectExtensions == null)
            {
                throw new Exception("reactiveObjectExtensions is null");
            }

            var raiseAndSetIfChangedMethod = ModuleDefinition.Import(reactiveObjectExtensions.Methods.Single(x => x.Name == "RaiseAndSetIfChanged"));

            if (raiseAndSetIfChangedMethod == null)
            {
                throw new Exception("raiseAndSetIfChangedMethod is null");
            }

            var reactiveAttribute = ModuleDefinition.FindType("ReactiveUI.Fody.Helpers", "ReactiveAttribute", helpers);

            if (reactiveAttribute == null)
            {
                throw new Exception("reactiveAttribute is null");
            }

            foreach (var targetType in targetTypes)
            {
                foreach (var property in targetType.Properties.Where(x => x.IsDefined(reactiveAttribute)).ToArray())
                {
                    if (property.SetMethod == null)
                    {
                        LogError($"Property {property.DeclaringType.FullName}.{property.Name} has no setter, therefore it is not possible for the property to change, and thus should not be marked with [Reactive]");
                        continue;
                    }

                    // Declare a field to store the property value
                    var field = new FieldDefinition("$" + property.Name, FieldAttributes.Private, property.PropertyType);
                    targetType.Fields.Add(field);

                    // Remove old field (the generated backing field for the auto property)
                    var oldField           = (FieldReference)property.GetMethod.Body.Instructions.Where(x => x.Operand is FieldReference).Single().Operand;
                    var oldFieldDefinition = oldField.Resolve();
                    targetType.Fields.Remove(oldFieldDefinition);

                    // See if there exists an initializer for the auto-property
                    var constructors = targetType.Methods.Where(x => x.IsConstructor);
                    foreach (var constructor in constructors)
                    {
                        var fieldAssignment = constructor.Body.Instructions.SingleOrDefault(x => Equals(x.Operand, oldFieldDefinition) || Equals(x.Operand, oldField));
                        if (fieldAssignment != null)
                        {
                            // Replace field assignment with a property set (the stack semantics are the same for both,
                            // so happily we don't have to manipulate the bytecode any further.)
                            var setterCall = constructor.Body.GetILProcessor().Create(property.SetMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, property.SetMethod);
                            constructor.Body.GetILProcessor().Replace(fieldAssignment, setterCall);
                        }
                    }

                    // Build out the getter which simply returns the value of the generated field
                    property.GetMethod.Body = new MethodBody(property.GetMethod);
                    property.GetMethod.Body.Emit(il =>
                    {
                        il.Emit(OpCodes.Ldarg_0);                                   // this
                        il.Emit(OpCodes.Ldfld, field.BindDefinition(targetType));   // pop -> this.$PropertyName
                        il.Emit(OpCodes.Ret);                                       // Return the field value that is lying on the stack
                    });

                    TypeReference genericTargetType = targetType;
                    if (targetType.HasGenericParameters)
                    {
                        var genericDeclaration = new GenericInstanceType(targetType);
                        foreach (var parameter in targetType.GenericParameters)
                        {
                            genericDeclaration.GenericArguments.Add(parameter);
                        }
                        genericTargetType = genericDeclaration;
                    }

                    var methodReference = raiseAndSetIfChangedMethod.MakeGenericMethod(genericTargetType, property.PropertyType);

                    // Build out the setter which fires the RaiseAndSetIfChanged method
                    if (property.SetMethod == null)
                    {
                        throw new Exception("[Reactive] is decorating " + property.DeclaringType.FullName + "." + property.Name + ", but the property has no setter so there would be nothing to react to.  Consider removing the attribute.");
                    }
                    property.SetMethod.Body = new MethodBody(property.SetMethod);
                    property.SetMethod.Body.Emit(il =>
                    {
                        il.Emit(OpCodes.Ldarg_0);                                   // this
                        il.Emit(OpCodes.Ldarg_0);                                   // this
                        il.Emit(OpCodes.Ldflda, field.BindDefinition(targetType));  // pop -> this.$PropertyName
                        il.Emit(OpCodes.Ldarg_1);                                   // value
                        il.Emit(OpCodes.Ldstr, property.Name);                      // "PropertyName"
                        il.Emit(OpCodes.Call, methodReference);                     // pop * 4 -> this.RaiseAndSetIfChanged(this.$PropertyName, value, "PropertyName")
                        il.Emit(OpCodes.Pop);                                       // We don't care about the result of RaiseAndSetIfChanged, so pop it off the stack (stack is now empty)
                        il.Emit(OpCodes.Ret);                                       // Return out of the function
                    });
                }
            }
        }
        public void Execute()
        {
            LogInfo("Obleak weaving");

            // Validate we have the dependencies we need
            var rxCore = ModuleDefinition.FindAssembly("System.Reactive.Core", LogError);

            if (rxCore == null)
            {
                return;
            }

            var mscorlib = ModuleDefinition.FindAssembly("mscorlib", LogError);

            if (mscorlib == null)
            {
                return;
            }

            var obleakCore = ModuleDefinition.FindAssembly("Obleak.Fody.Core", LogError);

            if (obleakCore == null)
            {
                return;
            }

            // Get the IDisposable type
            var disposableType = new TypeReference("System", "IDisposable", ModuleDefinition, mscorlib);

            // Get the CompositeDisposable type and required methods
            var compositeDisposableType         = new TypeReference("System.Reactive.Disposables", "CompositeDisposable", ModuleDefinition, rxCore);
            var compositeDisposableTypeResolved = compositeDisposableType.Resolve();

            if (compositeDisposableTypeResolved == null)
            {
                throw new Exception("compositeDisposableTypeResolved is null");
            }
            var compositeDisposableCtor          = ModuleDefinition.Import(compositeDisposableTypeResolved.Methods.Single(m => m.IsConstructor && !m.HasParameters));
            var compositeDisposableDisposeMethod = ModuleDefinition.Import(compositeDisposableTypeResolved.Methods.Single(m => m.Name == "Dispose"));

            var obleakAttribute = ModuleDefinition.FindType("Obleak.Fody.Core", "ObleakSubscriptionAttribute", obleakCore);

            if (obleakAttribute == null)
            {
                throw new Exception("obleakAttribute is null");
            }

            var disposableExtensions = new TypeReference("Obleak.Fody.Core", "Extensions", ModuleDefinition, obleakCore).Resolve();

            if (disposableExtensions == null)
            {
                throw new Exception("disposableExtensions is null");
            }

            var handleWithExtensionMethod = ModuleDefinition.Import(disposableExtensions.Methods.Single(x => x.Name == "HandleWith"));

            if (handleWithExtensionMethod == null)
            {
                throw new Exception("handleWithExtensionMethod is null");
            }

            // Any class where the Obleak attribute appears on the class, a constructor or a method
            var targets =
                ModuleDefinition.GetAllTypes()
                .Where(
                    x =>
                    x.IsDefined(obleakAttribute) ||
                    x.GetConstructors().Any(y => y.IsDefined(obleakAttribute)) ||
                    x.GetMethods().Any(y => y.IsDefined(obleakAttribute)));

            targets.ForEach(target =>
            {
                LogInfo($"Weaving target: {target.FullName}");
                // We can only weave classes which are disposable
                if (!target.IsDisposable())
                {
                    LogError($"Target class {target.FullName} is not disposable and therefore cannot be weaved with Obleak");
                    return;
                }

                // If this is class wide process every method, else only tackle what has been attributed. If the class + specific methods / constructors
                // have the attribute, still process everything

                // Note: we only need one composite disposable per target type
                var isClassWide = target.IsDefined(obleakAttribute);

                // The methods we're going to weave
                var methods = target.Methods.Where(x => isClassWide || x.IsDefined(obleakAttribute));

                // Declare a field for the composite disposable
                var compositeDisposableField = new FieldDefinition(COMPOSITE_DISPOSABLE_FIELD_NAME + target.Name, FieldAttributes.Private, compositeDisposableType);
                target.Fields.Add(compositeDisposableField);

                // Initialise this in all of the constructors in this class (not it's inheritance hierarchy) -- hence target.Methods usage
                target.Methods.Where(m => m.IsConstructor).ForEach(constructor =>
                {
                    constructor.Body.Emit(il =>
                    {
                        var first = constructor.Body.Instructions[0]; // first instruction

                        // Instructions equivalent of: this.$ObleakCompositeDisposable = new CompositeDisposable();
                        // But as it's done as the first instruction set when decompiled this is actually
                        // private CompositeDisposable $ObleakCompositeDisposable = new CompositeDisposable();
                        il.InsertBefore(first, il.Create(OpCodes.Ldarg_0));                                                // this
                        il.InsertBefore(first, il.Create(OpCodes.Newobj, compositeDisposableCtor));                        // new CompositeDisposable from ctor
                        il.InsertBefore(first, il.Create(OpCodes.Stfld, compositeDisposableField.BindDefinition(target))); // store new obj in fld
                    });
                });

                // For every .Subscribe() which returns a disposable call the .Add method on the compositeDisposableField
                methods.Where(m => m.HasBody && m.Body.Instructions.Any(i => _isExpectedMethodCall(i, disposableType, "Subscribe"))).ForEach(method =>
                {
                    method.Body.Emit(il =>
                    {
                        var subscribes = method.Body.Instructions.Where(i => _isExpectedMethodCall(i, disposableType, "Subscribe")).ToArray();
                        subscribes.ForEach(i =>
                        {
                            var next = i.Next;
                            il.InsertBefore(next, il.Create(OpCodes.Ldarg_0));                         // this
                            il.InsertBefore(next, il.Create(OpCodes.Ldfld, compositeDisposableField.BindDefinition(target)));
                            il.InsertBefore(next, il.Create(OpCodes.Call, handleWithExtensionMethod)); // Call .HandleWith($ObleakCompositeDisposable)
                        });
                    });
                });

                // Does the target itself have a dispose method or is it inheriting the implementation?
                // If doesn't have one of it's own we need to add one and call the base.Dispose() as we need to clean up this
                // new local composite disposable.
                var hasDisposeMethod = target.HasDisposeMethod();
                if (!hasDisposeMethod && target.HasGenericParameters)
                {
                    LogError($"Automatically generating a Dispose method for {target.Name} is not supported due to generics in it's inheritance hierarchy. " +
                             $"You need to create an empty parameterless Dispose() and re-build to use the Obleak weavers");
                    return;
                }
                if (!hasDisposeMethod)
                {
                    target.CreateDisposeMethod();
                }

                // Find the dispose method and append the instructions at the end to clean up the composite disposable
                var dispose = target.GetDisposeMethod();

                dispose.Body.Emit(il =>
                {
                    var last = dispose.Body.Instructions.Last(i => i.OpCode == OpCodes.Ret && (i.Next == null || dispose.Body.Instructions.Last() == i));
                    il.InsertBefore(last, il.Create(OpCodes.Ldarg_0));                                    // this
                    il.InsertBefore(last, il.Create(OpCodes.Ldfld, compositeDisposableField.BindDefinition(target)));
                    il.InsertBefore(last, il.Create(OpCodes.Callvirt, compositeDisposableDisposeMethod)); // call $ObleakCompositeDisposable.Dispose();
                });

                LogInfo($"Completed weaving target: {target.FullName}");
            });
        }
예제 #5
0
        public void Execute()
        {
            var reactiveUI     = ModuleDefinition.AssemblyReferences.Where(x => x.Name == "ReactiveUI").OrderByDescending(x => x.Version).FirstOrDefault();
            var helpers        = ModuleDefinition.AssemblyReferences.Where(x => x.Name == "Reactive.Fody.Helpers").OrderByDescending(x => x.Version).FirstOrDefault();
            var reactiveObject = new TypeReference("ReactiveUI", "IReactiveObject", ModuleDefinition, reactiveUI);

            var targetTypes = ModuleDefinition.GetAllTypes().Where(x => x.BaseType != null && reactiveObject.IsAssignableFrom(x.BaseType)).ToArray();

            var reactivePropertyExtensions = ModuleDefinition.FindType("Reactive.Fody.Helpers", "ReactivePropertyExtensions", helpers).Resolve();

            if (reactivePropertyExtensions == null)
            {
                throw new Exception("reactivePropertyExtensions is null");
            }

            var raiseAndSetIfChangedMethod = ModuleDefinition.ImportReference(reactivePropertyExtensions.Methods.Single(x => x.Name == "RaiseAndSetIfChanged"));

            if (raiseAndSetIfChangedMethod == null)
            {
                throw new Exception("raiseAndSetIfChangedMethod is null");
            }

            var reactiveAttribute = ModuleDefinition.FindType("Reactive.Fody.Helpers", "ReactiveAttribute", helpers);

            if (reactiveAttribute == null)
            {
                throw new Exception("reactiveAttribute is null");
            }

            var reactiveObjectExtensions = new TypeReference("ReactiveUI", "IReactiveObjectExtensions", ModuleDefinition, reactiveUI).Resolve();

            if (reactiveObjectExtensions == null)
            {
                throw new Exception("reactiveObjectExtensions is null");
            }

            var raisePropertyChangedMethod = ModuleDefinition.ImportReference(reactiveObjectExtensions.Methods.Single(x => x.Name == "RaisePropertyChanged"));

            if (raisePropertyChangedMethod == null)
            {
                throw new Exception("raisePropertyChangedMethod is null");
            }

            foreach (var targetType in targetTypes)
            {
                var setMethodByGetMethods = targetType.Properties
                                            .Where(x => x.SetMethod != null && x.GetMethod != null && x.IsDefined(reactiveAttribute))
                                            .ToDictionary(x => x.GetMethod, x => x.SetMethod);

                foreach (var property in targetType.Properties.Where(x => x.IsDefined(reactiveAttribute)).ToArray())
                {
                    TypeReference genericTargetType = targetType;
                    if (targetType.HasGenericParameters)
                    {
                        var genericDeclaration = new GenericInstanceType(targetType);
                        foreach (var parameter in targetType.GenericParameters)
                        {
                            genericDeclaration.GenericArguments.Add(parameter);
                        }
                        genericTargetType = genericDeclaration;
                    }

                    MethodDefinition[] getMethods;
                    if (property.SetMethod == null && property.GetMethod.TryGetMethodDependencies(out getMethods))
                    {
                        var setMethodsForGetInstructions = getMethods
                                                           .Where(x => setMethodByGetMethods.ContainsKey(x))
                                                           .Select(x => setMethodByGetMethods[x])
                                                           .ToArray();

                        if (!setMethodsForGetInstructions.Any())
                        {
                            LogError($"Get only Property {property.DeclaringType.FullName}.{property.Name} has no supported dependent properties. " +
                                     $"Only dependent auto properties decorated with the [Reactive] attribute can be weaved to raise property change on {property.Name}");
                        }

                        var raisePropertyChangedMethodReference = raisePropertyChangedMethod.MakeGenericMethod(genericTargetType);

                        foreach (var method in setMethodsForGetInstructions)
                        {
                            method.Body.Emit(il =>
                            {
                                var last = method.Body.Instructions.Last(i => i.OpCode == OpCodes.Ret);
                                il.InsertBefore(last, il.Create(OpCodes.Ldarg_0));
                                il.InsertBefore(last, il.Create(OpCodes.Ldstr, property.Name));
                                il.InsertBefore(last, il.Create(OpCodes.Call, raisePropertyChangedMethodReference));
                            });
                        }

                        // Move on to next property for the target type
                        continue;
                    }

                    if (property.SetMethod == null)
                    {
                        LogError($"Property {property.DeclaringType.FullName}.{property.Name} has no setter, therefore it is not possible for the property to change, and thus should not be marked with [Reactive]");
                        continue;
                    }

                    // Declare a field to store the property value
                    var field = new FieldDefinition("$" + property.Name, FieldAttributes.Private, property.PropertyType);
                    targetType.Fields.Add(field);

                    // Remove old field (the generated backing field for the auto property)
                    var oldField           = (FieldReference)property.GetMethod.Body.Instructions.Where(x => x.Operand is FieldReference).Single().Operand;
                    var oldFieldDefinition = oldField.Resolve();
                    targetType.Fields.Remove(oldFieldDefinition);

                    // See if there exists an initializer for the auto-property
                    var constructors = targetType.Methods.Where(x => x.IsConstructor);
                    foreach (var constructor in constructors)
                    {
                        var fieldAssignment = constructor.Body.Instructions.SingleOrDefault(x => Equals(x.Operand, oldFieldDefinition) || Equals(x.Operand, oldField));
                        if (fieldAssignment != null)
                        {
                            // Replace field assignment with a property set (the stack semantics are the same for both,
                            // so happily we don't have to manipulate the bytecode any further.)
                            var setterCall = constructor.Body.GetILProcessor().Create(property.SetMethod.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, property.SetMethod);
                            constructor.Body.GetILProcessor().Replace(fieldAssignment, setterCall);
                        }
                    }

                    // Build out the getter which simply returns the value of the generated field
                    property.GetMethod.Body = new MethodBody(property.GetMethod);
                    property.GetMethod.Body.Emit(il =>
                    {
                        il.Emit(OpCodes.Ldarg_0);                                   // this
                        il.Emit(OpCodes.Ldfld, field.BindDefinition(targetType));   // pop -> this.$PropertyName
                        il.Emit(OpCodes.Ret);                                       // Return the field value that is lying on the stack
                    });

                    var methodReference = raiseAndSetIfChangedMethod.MakeGenericMethod(genericTargetType, property.PropertyType);

                    // Build out the setter which fires the RaiseAndSetIfChanged method
                    if (property.SetMethod == null)
                    {
                        throw new Exception("[Reactive] is decorating " + property.DeclaringType.FullName + "." + property.Name + ", but the property has no setter so there would be nothing to react to.  Consider removing the attribute.");
                    }
                    property.SetMethod.Body = new MethodBody(property.SetMethod);
                    property.SetMethod.Body.Emit(il =>
                    {
                        RaiseAndSetIfChanged(methodReference, targetType, field, il, property.Name);
                    });
                }
            }
        }