using System; using System.Linq.Expressions; using System.Reflection; namespace NitroxModel.Helper; /// /// Utility class for reflection API. /// /// /// This class should be used when requiring or like information from code. This will ensure that compilation only succeeds /// when reflection is used properly. /// public static class Reflect { private static readonly BindingFlags BINDING_FLAGS_ALL = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; public static ConstructorInfo Constructor(Expression expression) { return (ConstructorInfo)GetMemberInfo(expression); } /// /// Given a lambda expression that calls a method, returns the method info. /// If method has parameters then anything can be supplied, the actual method won't be called. /// /// /// The expression. /// public static MethodInfo Method(Expression expression) { return (MethodInfo)GetMemberInfo(expression); } /// /// Given a lambda expression that calls a method, returns the method info. /// If method has parameters then anything can be supplied, the actual method won't be called. /// /// /// The expression. /// public static MethodInfo Method(Expression> expression) { return (MethodInfo)GetMemberInfo(expression, typeof(T)); } /// /// Given a lambda expression that calls a method, returns the method info. /// If method has parameters then anything can be supplied, the actual method won't be called. /// /// /// /// The expression. /// public static MethodInfo Method(Expression> expression) { return (MethodInfo)GetMemberInfo(expression, typeof(T)); } public static FieldInfo Field(Expression> expression) { return (FieldInfo)GetMemberInfo(expression); } public static FieldInfo Field(Expression> expression) { return (FieldInfo)GetMemberInfo(expression); } public static PropertyInfo Property(Expression> expression) { return (PropertyInfo)GetMemberInfo(expression); } public static PropertyInfo Property(Expression> expression) { return (PropertyInfo)GetMemberInfo(expression); } private static MemberInfo GetMemberInfo(LambdaExpression expression, Type implementingType = null) { Expression currentExpression = expression.Body; while (true) { switch (currentExpression.NodeType) { case ExpressionType.MemberAccess: // If it cannot be unwrapped further, return this member. MemberExpression exp = (MemberExpression)currentExpression; if (exp.Expression is null or ParameterExpression) { return exp.Member; } currentExpression = exp.Expression; break; case ExpressionType.UnaryPlus: currentExpression = ((UnaryExpression)currentExpression).Operand; break; case ExpressionType.New: return ((NewExpression)currentExpression).Constructor; case ExpressionType.Call: MethodInfo method = ((MethodCallExpression)currentExpression).Method; if (implementingType == null) { return method; } if (implementingType == method.ReflectedType) { return method; } // If method target is an interface, lookup the implementation in the implementingType. if (method.ReflectedType?.IsInterface ?? false) { InterfaceMapping interfaceMap = implementingType.GetInterfaceMap(method.ReflectedType); int i = Array.IndexOf(interfaceMap.InterfaceMethods, method); return interfaceMap.TargetMethods[i]; } // Expression does not know which type the MethodInfo belongs to if it's virtual; MethodInfo of base type/interface is returned instead. ParameterInfo[] parameters = method.GetParameters(); Type[] args = new Type[parameters.Length]; for (int i = 0; i < parameters.Length; i++) { args[i] = parameters[i].ParameterType; } return implementingType.GetMethod(method.Name, BINDING_FLAGS_ALL, null, args, null) ?? throw new Exception($"Unable to find method {method} on type {implementingType.FullName}"); case ExpressionType.Convert: case ExpressionType.ConvertChecked: currentExpression = ((UnaryExpression)currentExpression).Operand; break; case ExpressionType.Invoke: currentExpression = ((InvocationExpression)currentExpression).Expression; break; default: throw new ArgumentException($"Lambda expression '{expression}' does not target a member"); } } } /// /// Used to supplement ref/out/in parameters when using the API. /// public struct Ref { public static T Field; } }