using Esiur.Resource; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Text; namespace Esiur.Proxy { public static class ResourceProxy { static Dictionary cache = new Dictionary(); #if NETSTANDARD1_5 static MethodInfo modifyMethod = typeof(Instance).GetTypeInfo().GetMethod("Modified"); static MethodInfo instanceGet = typeof(IResource).GetTypeInfo().GetProperty("Instance").GetGetMethod(); #else static MethodInfo modifyMethod = typeof(Instance).GetMethod("Modified"); static MethodInfo instanceGet = typeof(IResource).GetProperty("Instance").GetGetMethod(); #endif public static Type GetProxy(Type type) { if (cache.ContainsKey(type)) return cache[type]; #if NETSTANDARD1_5 var typeInfo = type.GetTypeInfo(); if (typeInfo.IsSealed) throw new Exception("Sealed class can't be proxied."); var props = from p in typeInfo.GetProperties() where p.CanWrite && p.GetSetMethod().IsVirtual && p.GetCustomAttributes(typeof(ResourceProperty), false).Count() > 0 select p; #else if (type.IsSealed) throw new Exception("Sealed class can't be proxied."); var props = from p in type.GetProperties() where p.CanWrite && p.GetSetMethod().IsVirtual && p.GetCustomAttributes(typeof(ResourceProperty), false).Count() > 0 select p; #endif var assemblyName = new AssemblyName("Esiur.Proxy.T." + type.Namespace); var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name); var typeName = Assembly.CreateQualifiedName(assemblyName.FullName, type.Name); var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class, type); foreach (PropertyInfo propertyInfo in props) CreateProperty(propertyInfo, typeBuilder); #if NETSTANDARD1_5 var t = typeBuilder.CreateTypeInfo().AsType(); cache.Add(type, t); return t; #else var t = typeBuilder.CreateType(); cache.Add(type, t); return t; #endif } public static Type GetProxy() where T : IResource { return GetProxy(typeof(T)); } private static void CreateProperty(PropertyInfo pi, TypeBuilder typeBuilder) { var propertyBuilder = typeBuilder.DefineProperty(pi.Name, PropertyAttributes.None, pi.PropertyType, null); // Create set method MethodBuilder builder = typeBuilder.DefineMethod("set_" + pi.Name, MethodAttributes.Public | MethodAttributes.Virtual, null, new Type[] { pi.PropertyType }); builder.DefineParameter(1, ParameterAttributes.None, "value"); ILGenerator g = builder.GetILGenerator(); Label callModified = g.DefineLabel(); Label exitMethod = g.DefineLabel(); /* IL_0000: ldarg.0 IL_0001: call instance class [Esiur]Esiur.Resource.Instance [Esiur]Esiur.Resource.Resource::get_Instance() // (no C# code) IL_0006: dup IL_0007: brtrue.s IL_000c IL_0009: pop // } IL_000a: br.s IL_0017 // (no C# code) IL_000c: ldstr "Level3" IL_0011: call instance void [Esiur]Esiur.Resource.Instance::Modified(string) IL_0016: nop IL_0017: ret */ // Add IL code for set method g.Emit(OpCodes.Nop); g.Emit(OpCodes.Ldarg_0); g.Emit(OpCodes.Ldarg_1); g.Emit(OpCodes.Call, pi.GetSetMethod()); /* IL_0000: ldarg.0 IL_0001: call instance class [Esiur]Esiur.Resource.Instance [Esiur]Esiur.Resource.Resource::get_Instance() IL_0006: ldstr "Level3" IL_000b: callvirt instance void [Esiur]Esiur.Resource.Instance::Modified(string) IL_0010: ret */ // Call property changed for object g.Emit(OpCodes.Nop); g.Emit(OpCodes.Ldarg_0); g.Emit(OpCodes.Call, instanceGet); g.Emit(OpCodes.Dup); g.Emit(OpCodes.Brtrue_S, callModified); g.Emit(OpCodes.Pop); g.Emit(OpCodes.Br_S, exitMethod); g.MarkLabel(callModified); g.Emit(OpCodes.Ldstr, pi.Name); g.Emit(OpCodes.Callvirt, modifyMethod); g.Emit(OpCodes.Nop); g.MarkLabel(exitMethod); g.Emit(OpCodes.Ret); propertyBuilder.SetSetMethod(builder); } } }