using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; namespace Umbraco.Core { /// /// Provides utilities to simplify reflection. /// public static class ReflectionUtilities { public const AssemblyBuilderAccess DefaultAssemblyBuilderAccess = AssemblyBuilderAccess.Run; public const AssemblyBuilderAccess NoAssembly = 0; public static Func GetPropertyGetter(string propertyName) { var type = typeof (TInstance); var type0 = typeof (TValue); var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (property == null || property.PropertyType != type0) throw new InvalidOperationException($"Could not get property {type}.{propertyName} : {type0}."); return GetPropertyGetter(property); } public static Action GetPropertySetter(string propertyName) { var type = typeof (TInstance); var type0 = typeof (TValue); var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (property == null || property.PropertyType != type0) throw new InvalidOperationException($"Could not get property {type}.{propertyName} : {type0}."); return GetPropertySetter(property); } public static void GetPropertyGetterSetter(string propertyName, out Func getter, out Action setter) { var type = typeof (TInstance); var type0 = typeof (TValue); var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (property == null || property.PropertyType != type0) throw new InvalidOperationException($"Could not get property {type}.{propertyName} : {type0}."); getter = GetPropertyGetter(property); setter = GetPropertySetter(property); } public static Expression> GetCtorExpr(bool mustExist = true) { var type = typeof (TInstance); // get the constructor infos var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null); if (ctor == null) { if (!mustExist) return null; throw new InvalidOperationException($"Could not find constructor {type}.ctor()."); } var exprNew = Expression.New(ctor); return Expression.Lambda>(exprNew); } public static Expression GetCtorExpr(Type type, bool mustExist = true) { // get the constructor infos var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null); if (ctor == null) { if (!mustExist) return null; throw new InvalidOperationException($"Could not find constructor {type}.ctor()."); } var exprNew = Expression.New(ctor); return Expression.Lambda(exprNew); } public static Func GetCtor(bool mustExist = true, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { var expr = GetCtorExpr(mustExist); if (expr == null) return null; return access == NoAssembly ? expr.Compile() : CompileToDelegate(expr, access); } public static TLambda GetCtor(Type type, bool mustExist = true, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { var expr = GetCtorExpr(type, mustExist); if (expr == null) return default(TLambda); return access == NoAssembly ? expr.Compile() : CompileToDelegate(expr, access); } public static Func EmitCtor(bool mustExist = true) { var type = typeof (TInstance); // get the constructor infos var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null); if (ctor == null) { if (!mustExist) return null; throw new InvalidOperationException($"Could not find constructor {type}.ctor()."); } var dm = new DynamicMethod(string.Empty, type, Array.Empty(), type.Module, true); var gen = dm.GetILGenerator(); gen.Emit(OpCodes.Newobj, ctor); gen.Emit(OpCodes.Ret); return (Func) dm.CreateDelegate(typeof (Func)); } public static Func EmitCtor(bool mustExist = true) { var type = typeof (TInstance); var type0 = typeof (TArg0); // get the constructor infos var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, new[] { type0 }, null); if (ctor == null) { if (!mustExist) return null; throw new InvalidOperationException($"Could not find constructor {type}.ctor()."); } var dm = new DynamicMethod(string.Empty, type, new[] { type0 }, type.Module, true); var gen = dm.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Newobj, ctor); gen.Emit(OpCodes.Ret); return (Func) dm.CreateDelegate(typeof (Func)); } public static Func EmitCtor(Type type, bool mustExist = true) { var type0 = typeof (TArg0); // get the constructor infos var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, new[] { type0 }, null); if (ctor == null) { if (!mustExist) return null; throw new InvalidOperationException($"Could not find constructor {type}.ctor()."); } var dm = new DynamicMethod(string.Empty, type, new[] { type0 }, type.Module, true); var gen = dm.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Newobj, ctor); gen.Emit(OpCodes.Ret); return (Func) dm.CreateDelegate(typeof (Func)); } public static TLambda EmitCtor(ConstructorInfo constructorInfo) { var type = constructorInfo.DeclaringType; var args = constructorInfo.GetParameters().Select(x => x.ParameterType).ToArray(); var dm = new DynamicMethod(string.Empty, type, args, type.Module, true); var gen = dm.GetILGenerator(); if (args.Length < 5) { if (args.Length > 0) gen.Emit(OpCodes.Ldarg_0); if (args.Length > 1) gen.Emit(OpCodes.Ldarg_1); if (args.Length > 2) gen.Emit(OpCodes.Ldarg_2); if (args.Length > 3) gen.Emit(OpCodes.Ldarg_3); } else { for (var i = 0; i < args.Length; i++) gen.Emit(OpCodes.Ldarg, i); } gen.Emit(OpCodes.Newobj, constructorInfo); gen.Emit(OpCodes.Ret); return (TLambda) (object) dm.CreateDelegate(typeof (TLambda)); } // fixme others need it too? public static Func GetCtor() { var type = typeof (TInstance); var type0 = typeof (TArg0); // get the constructor infos var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, new[] { type0 }, null); if (ctor == null) throw new InvalidOperationException($"Could not find constructor {type}.ctor({type0})."); var exprArgs = ctor.GetParameters().Select(x => Expression.Parameter(x.ParameterType, x.Name)).ToArray(); // ReSharper disable once CoVariantArrayConversion var exprNew = Expression.New(ctor, exprArgs); var expr = Expression.Lambda>(exprNew, exprArgs); return expr.CompileToDelegate(); } public static Func GetCtor() { var type = typeof (TInstance); var type0 = typeof (TArg0); var type1 = typeof (TArg1); // get the constructor infos var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, new[] { type0, type1 }, null); if (ctor == null) throw new InvalidOperationException($"Could not find constructor {type}.ctor({type0}, {type1})."); var exprArgs = ctor.GetParameters().Select(x => Expression.Parameter(x.ParameterType, x.Name)).ToArray(); // ReSharper disable once CoVariantArrayConversion var exprNew = Expression.New(ctor, exprArgs); var expr = Expression.Lambda>(exprNew, exprArgs); return expr.CompileToDelegate(); } public static TMethod GetMethod(MethodInfo method) { var type = method.DeclaringType; GetMethodParms(out var parameterTypes, out var returnType); return GetStaticMethod(method, method.Name, type, parameterTypes, returnType); } public static TMethod GetMethod(MethodInfo method) { var type = method.DeclaringType; GetMethodParms(out var parameterTypes, out var returnType); return GetMethod(method, method.Name, type, parameterTypes, returnType); } public static TMethod GetMethod(string methodName) { var type = typeof (TInstance); GetMethodParms(out var parameterTypes, out var returnType); var method = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, parameterTypes, null); return GetMethod(method, methodName, type, parameterTypes, returnType); } private static Func GetPropertyGetter(PropertyInfo property) { var type = typeof (TInstance); var getMethod = property.GetMethod; if (getMethod == null) throw new InvalidOperationException($"Property {type}.{property.Name} : {property.PropertyType} does not have a getter."); var exprThis = Expression.Parameter(type, "this"); var exprCall = Expression.Call(exprThis, getMethod); var expr = Expression.Lambda>(exprCall, exprThis); return expr.CompileToDelegate(); } private static Action GetPropertySetter(PropertyInfo property) { var type = typeof (TInstance); var setMethod = property.SetMethod; if (setMethod == null) throw new InvalidOperationException($"Property {type}.{property.Name} : {property.PropertyType} does not have a setter."); var exprThis = Expression.Parameter(type, "this"); var exprArg0 = Expression.Parameter(typeof (TValue), "value"); var exprCall = Expression.Call(exprThis, setMethod, exprArg0); var expr = Expression.Lambda>(exprCall, exprThis, exprArg0); return expr.CompileToDelegate(); } private static void GetMethodParms(out Type[] parameterTypes, out Type returnType) { var typeM = typeof (TMethod); var typeList = new List(); returnType = typeof (void); if (!typeof (MulticastDelegate).IsAssignableFrom(typeM) || typeM == typeof (MulticastDelegate)) throw new InvalidOperationException("Invalid TMethod, must be a Func or Action."); var typeName = typeM.FullName; if (typeName == null) throw new InvalidOperationException($"Could not get {typeM} type name."); if (typeName.StartsWith("System.Func`")) { var i = 0; while (i < typeM.GenericTypeArguments.Length - 1) typeList.Add(typeM.GenericTypeArguments[i++]); returnType = typeM.GenericTypeArguments[i]; } else if (typeName.StartsWith("System.Action`")) { var i = 0; while (i < typeM.GenericTypeArguments.Length) typeList.Add(typeM.GenericTypeArguments[i++]); } else if (typeName == "System.Action") { // no args } else throw new InvalidOperationException(typeName); parameterTypes = typeList.ToArray(); } private static void GetMethodParms(out Type[] parameterTypes, out Type returnType) { var type = typeof (TInstance); var typeM = typeof (TMethod); var typeList = new List(); returnType = typeof (void); if (!typeof (MulticastDelegate).IsAssignableFrom(typeM) || typeM == typeof (MulticastDelegate)) throw new InvalidOperationException("Invalid TMethod, must be a Func or Action."); var typeName = typeM.FullName; if (typeName == null) throw new InvalidOperationException($"Could not get {typeM} type name."); if (!typeM.IsGenericType) throw new InvalidOperationException($"Type {typeName} is not generic."); if (typeM.GenericTypeArguments[0] != type) throw new InvalidOperationException($"Invalid type {typeName}, the first generic argument must be {type.FullName}."); if (typeName.StartsWith("System.Func`")) { var i = 1; while (i < typeM.GenericTypeArguments.Length - 1) typeList.Add(typeM.GenericTypeArguments[i++]); returnType = typeM.GenericTypeArguments[i]; } else if (typeName.StartsWith("System.Action`")) { var i = 1; while (i < typeM.GenericTypeArguments.Length) typeList.Add(typeM.GenericTypeArguments[i++]); } else throw new InvalidOperationException(typeName); parameterTypes = typeList.ToArray(); } private static TMethod GetStaticMethod(MethodInfo method, string methodName, Type type, Type[] parameterTypes, Type returnType) { if (method == null || method.ReturnType != returnType) throw new InvalidOperationException($"Could not find static method {type}.{methodName}({string.Join(",", parameterTypes.Select(x => x.ToString()))}) : {returnType}"); var e = new List(); foreach (var p in method.GetParameters()) e.Add(Expression.Parameter(p.ParameterType, p.Name)); var exprCallArgs = e.ToArray(); var exprLambdaArgs = exprCallArgs; // ReSharper disable once CoVariantArrayConversion var exprCall = Expression.Call(method, exprCallArgs); var expr = Expression.Lambda(exprCall, exprLambdaArgs); return expr.CompileToDelegate(); } private static TMethod GetMethod(MethodInfo method, string methodName, Type type, Type[] parameterTypes, Type returnType) { if (method == null || method.ReturnType != returnType) throw new InvalidOperationException($"Could not find method {type}.{methodName}({string.Join(",", parameterTypes.Select(x => x.ToString()))}) : {returnType}"); var e = new List(); foreach (var p in method.GetParameters()) e.Add(Expression.Parameter(p.ParameterType, p.Name)); var exprCallArgs = e.ToArray(); var exprThis = Expression.Parameter(type, "this"); e.Insert(0, exprThis); var exprLambdaArgs = e.ToArray(); // ReSharper disable once CoVariantArrayConversion var exprCall = Expression.Call(exprThis, method, exprCallArgs); var expr = Expression.Lambda(exprCall, exprLambdaArgs); return expr.Compile(); } // not sure we want this at all? /* public static object GetStaticProperty(this Type type, string propertyName, Func, PropertyInfo> filter = null) { var propertyInfo = GetPropertyInfo(type, propertyName, filter); if (propertyInfo == null) throw new ArgumentOutOfRangeException(nameof(propertyName), $"Couldn't find property {propertyName} in type {type.FullName}"); return propertyInfo.GetValue(null, null); } public static object CallStaticMethod(this Type type, string methodName, params object[] parameters) { var methodInfo = GetMethodInfo(type, methodName); if (methodInfo == null) throw new ArgumentOutOfRangeException(nameof(methodName), $"Couldn't find method {methodName} in type {type.FullName}"); return methodInfo.Invoke(null, parameters); } public static object CallMethod(this object obj, string methodName, params object[] parameters) { if (obj == null) throw new ArgumentNullException(nameof(obj)); Type type = obj.GetType(); var methodInfo = GetMethodInfo(type, methodName); if (methodInfo == null) throw new ArgumentOutOfRangeException(nameof(methodName), $"Couldn't find method {methodName} in type {type.FullName}"); return methodInfo.Invoke(obj, parameters); } public static object CallMethod(this object obj, string methodName, Func, MethodInfo> filter = null, params object[] parameters) { if (obj == null) throw new ArgumentNullException(nameof(obj)); Type type = obj.GetType(); var methodInfo = GetMethodInfo(type, methodName, filter); if (methodInfo == null) throw new ArgumentOutOfRangeException(nameof(methodName), $"Couldn't find method {methodName} in type {type.FullName}"); return methodInfo.Invoke(obj, parameters); } private static MethodInfo GetMethodInfo(Type type, string methodName, Func, MethodInfo> filter = null) { MethodInfo methodInfo; do { try { methodInfo = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); } catch (AmbiguousMatchException) { if (filter == null) throw; methodInfo = filter( type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static) .Where(x => x.Name == methodName)); } type = type.BaseType; } while (methodInfo == null && type != null); return methodInfo; } private static PropertyInfo GetPropertyInfo(Type type, string propertyName, Func, PropertyInfo> filter = null) { PropertyInfo propInfo; do { try { propInfo = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); } catch (AmbiguousMatchException) { if (filter == null) throw; propInfo = filter(type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static) .Where(x => x.Name == propertyName)); } type = type.BaseType; } while (propInfo == null && type != null); return propInfo; } public static object GetPropertyValue(this object obj, string propertyName) { if (obj == null) throw new ArgumentNullException(nameof(obj)); Type objType = obj.GetType(); PropertyInfo propInfo = GetPropertyInfo(objType, propertyName); if (propInfo == null) throw new ArgumentOutOfRangeException(nameof(propertyName), $"Couldn't find property {propertyName} in type {objType.FullName}"); return propInfo.GetValue(obj, null); } public static object GetPropertyValue(this object obj, string propertyName, IDictionary propCache) { if (obj == null) throw new ArgumentNullException(nameof(obj)); Type objType = obj.GetType(); PropertyInfo propInfo; if (propCache.ContainsKey(propertyName)) { propInfo = propCache[propertyName]; } else { propInfo = GetPropertyInfo(objType, propertyName); if (propInfo == null) throw new ArgumentOutOfRangeException(nameof(propertyName), $"Couldn't find property {propertyName} in type {objType.FullName}"); propCache[propertyName] = propInfo; } return propInfo.GetValue(obj, null); } public static void SetPropertyValue(this object obj, string propertyName, object val) { if (obj == null) throw new ArgumentNullException(nameof(obj)); Type objType = obj.GetType(); PropertyInfo propInfo = GetPropertyInfo(objType, propertyName); if (propInfo == null) throw new ArgumentOutOfRangeException(nameof(propertyName), $"Couldn't find property {propertyName} in type {objType.FullName}"); propInfo.SetValue(obj, val, null); } public static void SetPropertyValue(this object obj, string propertyName, object val, IDictionary propCache) { if (obj == null) throw new ArgumentNullException(nameof(obj)); if (propCache == null) throw new ArgumentNullException(nameof(propCache)); Type objType = obj.GetType(); PropertyInfo propInfo; if (propCache.ContainsKey(propertyName)) { propInfo = propCache[propertyName]; } else { propInfo = GetPropertyInfo(objType, propertyName); if (propInfo == null) throw new ArgumentOutOfRangeException(nameof(propertyName), $"Couldn't find property {propertyName} in type {objType.FullName}"); propCache[propertyName] = propInfo; } propInfo.SetValue(obj, val, null); } */ // fixme dont think this can work at all public static TLambda Compile(Expression expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { return access == NoAssembly ? expr.Compile() : CompileToDelegate(expr, access); } public static Action CompileToDelegate(Expression expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static); // CompileToMethod requires a static method expr.CompileToMethod(builder); return (Action) Delegate.CreateDelegate(typeof (Action), typeBuilder.CreateType().GetMethod("Method")); } public static Action CompileToDelegate(Expression> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method typeof (void), new[] { typeof (T1) }); expr.CompileToMethod(builder); return (Action) Delegate.CreateDelegate(typeof (Action), typeBuilder.CreateType().GetMethod("Method")); } public static Action CompileToDelegate(Expression> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method typeof (void), new[] { typeof (T1), typeof (T2) }); expr.CompileToMethod(builder); return (Action) Delegate.CreateDelegate(typeof (Action), typeBuilder.CreateType().GetMethod("Method")); } public static Action CompileToDelegate(Expression> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method typeof (void), new[] { typeof (T1), typeof (T2), typeof (T3) }); expr.CompileToMethod(builder); return (Action) Delegate.CreateDelegate(typeof (Action), typeBuilder.CreateType().GetMethod("Method")); } public static Func CompileToDelegate(Expression> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, typeof (TResult), Array.Empty()); // CompileToMethod requires a static method expr.CompileToMethod(builder); return (Func) Delegate.CreateDelegate(typeof (Func), typeBuilder.CreateType().GetMethod("Method")); } public static Func CompileToDelegate(Expression> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method typeof (TResult), new[] { typeof (T1) }); expr.CompileToMethod(builder); return (Func) Delegate.CreateDelegate(typeof (Func), typeBuilder.CreateType().GetMethod("Method")); } public static Func CompileToDelegate(Expression> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method typeof (TResult), new[] { typeof (T1), typeof (T2) }); expr.CompileToMethod(builder); return (Func) Delegate.CreateDelegate(typeof (Func), typeBuilder.CreateType().GetMethod("Method")); } public static Func CompileToDelegate(Expression> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method typeof (TResult), new[] { typeof (T1), typeof (T2), typeof (T3) }); expr.CompileToMethod(builder); return (Func) Delegate.CreateDelegate(typeof (Func), typeBuilder.CreateType().GetMethod("Method")); } public static TMethod CompileToDelegate(Expression expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { var typeBuilder = CreateTypeBuilder(access); GetMethodParms(out var parameterTypes, out var returnType); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method returnType, parameterTypes); expr.CompileToMethod(builder); return (TMethod) (object) Delegate.CreateDelegate(typeof (TMethod), typeBuilder.CreateType().GetMethod("Method")); } public static TMethod[] CompileToDelegates(params Expression[] exprs) => CompileToDelegates(AssemblyBuilderAccess.RunAndCollect, exprs); public static TMethod[] CompileToDelegates(AssemblyBuilderAccess access, params Expression[] exprs) { var typeBuilder = CreateTypeBuilder(access); GetMethodParms(out var parameterTypes, out var returnType); var i = 0; foreach (var expr in exprs) { var builder = typeBuilder.DefineMethod($"Method_{i++}", MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method returnType, parameterTypes); expr.CompileToMethod(builder); } var type = typeBuilder.CreateType(); var methods = new TMethod[exprs.Length]; for (i = 0; i < exprs.Length; i++) methods[i] = (TMethod) (object) Delegate.CreateDelegate(typeof (TMethod), type.GetMethod($"Method_{i++}")); return methods; } private static TypeBuilder CreateTypeBuilder(AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { var assemblyName = new AssemblyName("Umbraco.Core.DynamicAssemblies." + Guid.NewGuid().ToString("N")); var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, access); var module = (access & AssemblyBuilderAccess.Save) > 0 ? assembly.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll") : assembly.DefineDynamicModule(assemblyName.Name); // has to be transient return module.DefineType("Class", TypeAttributes.Public | TypeAttributes.Abstract); } } }