/* private AssemblyNameReference FindSexyReactAssembly() { var sexyReact = ModuleDefinition.FindAssembly("SexyReact"); if (sexyReact != null) return sexyReact; var assemblies = ModuleDefinition.AssemblyReferences.ToArray(); foreach (var assembly in assemblies) { var reactiveObject = new TypeReference("SexyReact", "IRxObject", ModuleDefinition, assembly); if (reactiveObject.Resolve() != null) { return assembly; } } return null; } */ public void Execute() { CecilExtensions.LogInfo = LogInfo; var sexyReact = ModuleDefinition.FindAssembly("SexyReact"); if (sexyReact == null) { LogInfo("Could not find assembly: SexyReact (" + string.Join(", ", ModuleDefinition.AssemblyReferences.Select(x => x.Name)) + ")"); return; } LogInfo($"{sexyReact.Name} {sexyReact.Version}"); var reactiveObject = new TypeReference("SexyReact", "IRxObject", ModuleDefinition, sexyReact); var targetTypes = ModuleDefinition.GetAllTypes().Where(x => x.BaseType != null && reactiveObject.IsAssignableFrom(x.BaseType)).ToArray(); var propertyInfoType = ModuleDefinition.Import(typeof(PropertyInfo)); // LogInfo($"propertyInfoType: {propertyInfoType}"); var getMethod = ModuleDefinition.Import(reactiveObject.Resolve().Methods.SingleOrDefault(x => x.Name == "Get")); if (getMethod == null) throw new Exception("getMethod is null"); var setMethod = ModuleDefinition.Import(reactiveObject.Resolve().Methods.SingleOrDefault(x => x.Name == "Set")); if (setMethod == null) throw new Exception("setMethod is null"); var reactiveAttribute = ModuleDefinition.FindType("SexyReact", "RxAttribute", sexyReact); if (reactiveAttribute == null) throw new Exception("reactiveAttribute is null"); var typeType = ModuleDefinition.Import(typeof(Type)); var getPropertyByName = ModuleDefinition.Import(typeType.Resolve().Methods.Single(x => x.Name == "GetProperty" && x.Parameters.Count == 1)); var getTypeFromTypeHandle = ModuleDefinition.Import(typeType.Resolve().Methods.Single(x => x.Name == "GetTypeFromHandle")); foreach (var targetType in targetTypes) { var rxForClass = targetType.IsDefined(reactiveAttribute, true); var logger = rxForClass ? LogInfo : LogWarning; PropertyDefinition[] properties; // If [Rx] has been applied to a class, then all of its properties are considered Rx if (rxForClass) { var mostAncestralClassWithRx = targetType.GetEarliestAncestorThatDeclares(reactiveAttribute); properties = targetType.Properties.Where(x => mostAncestralClassWithRx.IsAssignableFrom(x.DeclaringType)).ToArray(); } // Otherwise, only properties decorated with [Rx] are considered else { properties = targetType.Properties.Where(x => x.IsDefined(reactiveAttribute)).ToArray(); } if (properties.Any()) { var staticConstructor = targetType.GetStaticConstructor(); if (staticConstructor == null) { // LogInfo("Creating static constructor"); staticConstructor = new MethodDefinition(".cctor", MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, ModuleDefinition.TypeSystem.Void); staticConstructor.Body = new MethodBody(staticConstructor); targetType.Methods.Add(staticConstructor); } else { staticConstructor.Body.Instructions.RemoveAt(staticConstructor.Body.Instructions.Count - 1); } foreach (var property in properties) { LogInfo($"{targetType}.{property}"); if (property.GetMethod == null || property.SetMethod == null) { logger($"Rx properties must have both a getter and a setter. Skipping {targetType}.{property.Name}"); continue; } // Remove old field (the generated backing field for the auto property) var oldFieldInstruction = property.GetMethod.Body.Instructions.Where(x => x.Operand is FieldReference).SingleOrDefault(); if (oldFieldInstruction == null) { logger($"Rx properties must be auto-properties. The backing field for property {targetType}.{property.Name} not found."); continue; } 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); } } // Add a static field for the property's property info var propertyInfoField = new FieldDefinition(property.Name + "$PropertyInfo", FieldAttributes.Private | FieldAttributes.Static, propertyInfoType); targetType.Fields.Add(propertyInfoField); staticConstructor.Body.Emit(il => { il.Emit(OpCodes.Ldtoken, targetType); il.Emit(OpCodes.Call, getTypeFromTypeHandle); il.Emit(OpCodes.Ldstr, property.Name); il.Emit(OpCodes.Call, getPropertyByName); il.Emit(OpCodes.Stsfld, propertyInfoField); }); // Build out the getter which returns Get<TValue>(property.Name$PropertyInfo); property.GetMethod.Body = new MethodBody(property.GetMethod); var getMethodReference = getMethod.MakeGenericMethod(property.PropertyType); property.GetMethod.Body.Emit(il => { il.Emit(OpCodes.Ldarg_0); // this il.Emit(OpCodes.Ldsfld, propertyInfoField); // property.Name$PropertyInfo il.Emit(OpCodes.Callvirt, getMethodReference); // pop * 2 -> this.Get(property.Name$PropertyName) il.Emit(OpCodes.Ret); // Return the field value that is lying on the stack }); var setMethodReference = setMethod.MakeGenericMethod(property.PropertyType); // Build out the setter which fires the RaiseAndSetIfChanged method property.SetMethod.Body = new MethodBody(property.SetMethod); property.SetMethod.Body.Emit(il => { il.Emit(OpCodes.Ldarg_0); // this il.Emit(OpCodes.Ldsfld, propertyInfoField); // property.Name$PropertyInfo il.Emit(OpCodes.Ldarg_1); // value il.Emit(OpCodes.Callvirt, setMethodReference); // pop * 2 -> this.Get(property.Name$PropertyName) il.Emit(OpCodes.Ret); // return out of the method }); } staticConstructor.Body.Emit(il => { il.Emit(OpCodes.Ret); }); } } }
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(string.Format("{0} {1}", reactiveUI.Name, reactiveUI.Version)); var helpers = ModuleDefinition.AssemblyReferences.Where(x => x.Name == "ReactiveUI.Fody.Helpers").OrderByDescending(x => x.Version).FirstOrDefault(); if (helpers == null) throw new Exception("Could not find assembly: ReactiveUI.Fody.Helpers (" + string.Join(", ", ModuleDefinition.AssemblyReferences.Select(x => x.Name)) + ")"); LogInfo(string.Format("{0} {1}", 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()) { // 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 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 }); } } }