Files
Nitrox/NitroxModel/Helper/Reflect.cs
2025-07-06 00:23:46 +02:00

148 lines
6.0 KiB
C#

using System;
using System.Linq.Expressions;
using System.Reflection;
namespace NitroxModel.Helper;
/// <summary>
/// Utility class for reflection API.
/// </summary>
/// <remarks>
/// This class should be used when requiring <see cref="MethodInfo" /> or <see cref="MemberInfo" /> like information from code. This will ensure that compilation only succeeds
/// when reflection is used properly.
/// </remarks>
public static class Reflect
{
private static readonly BindingFlags BINDING_FLAGS_ALL = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
public static ConstructorInfo Constructor(Expression<Action> expression)
{
return (ConstructorInfo)GetMemberInfo(expression);
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression">The expression.</param>
/// <returns></returns>
public static MethodInfo Method(Expression<Action> expression)
{
return (MethodInfo)GetMemberInfo(expression);
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression">The expression.</param>
/// <returns></returns>
public static MethodInfo Method<T>(Expression<Action<T>> expression)
{
return (MethodInfo)GetMemberInfo(expression, typeof(T));
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="expression">The expression.</param>
/// <returns></returns>
public static MethodInfo Method<T, TResult>(Expression<Func<T, TResult>> expression)
{
return (MethodInfo)GetMemberInfo(expression, typeof(T));
}
public static FieldInfo Field<T>(Expression<Func<T>> expression)
{
return (FieldInfo)GetMemberInfo(expression);
}
public static FieldInfo Field<T>(Expression<Func<T, object>> expression)
{
return (FieldInfo)GetMemberInfo(expression);
}
public static PropertyInfo Property<T>(Expression<Func<T>> expression)
{
return (PropertyInfo)GetMemberInfo(expression);
}
public static PropertyInfo Property<T>(Expression<Func<T, object>> 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");
}
}
}
/// <summary>
/// Used to supplement ref/out/in parameters when using the <see cref="Reflect" /> API.
/// </summary>
public struct Ref<T>
{
public static T Field;
}
}