Files
TheVVaS-Assets/RPGCoreCommon/Helpers/Runtime/ReflectionExtensions.cs
T
2026-04-25 23:37:10 +02:00

168 lines
7.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using RPGCoreCommon.Helpers.Exceptions;
namespace RPGCoreCommon.Helpers
{
public static class ReflectionExtensions
{
public const BindingFlags DefaultBindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
/// <summary>Works with any member (type, field, property, method)</summary>
/// <returns>type of this member's value</returns>
public static Type GetMemberValueType(this MemberInfo memberInfo)
{
return memberInfo switch
{
Type type => type,
FieldInfo fieldInfo => fieldInfo.FieldType,
PropertyInfo propertyInfo => propertyInfo.PropertyType,
MethodInfo methodInfo => methodInfo.ReturnType,
_ => null
};
}
/// <summary>Works with any member (type, field, property, method)</summary>
/// <param name="memberInfo">this member</param>
/// <param name="obj">Object to use for this reflection</param>
/// <returns>member's value</returns>
public static object GetMemberValue(this MemberInfo memberInfo, object obj)
{
return memberInfo switch
{
Type => obj,
FieldInfo fi => fi.GetValue(obj),
PropertyInfo pi => pi.GetValue(obj, null),
MethodInfo mi => mi.Invoke(obj, null),
_ => null
};
}
/// <summary>Works with any member (type, field, property, method)</summary>
/// <param name="memberInfo">this member</param>
/// <param name="path">path from current member with dot "." as delimiter</param>
/// <param name="bindingFlags">Custom binding flags to use</param>
/// <returns>TYPE of member's value from given path</returns>
/// <exception cref="MemberNotFoundException">when member is not found</exception>
public static Type GetMemberValueTypeByPath(this MemberInfo memberInfo, string path, BindingFlags bindingFlags = DefaultBindingFlags)
{
var pathParts = path.Split('.');
var memberType = memberInfo.GetMemberValueType();
foreach (var pathPart in pathParts)
{
var member = memberType.GetMember(pathPart, bindingFlags).FirstOrDefault();
if (member == null) throw new MemberNotFoundException(path, memberType.GetMemberValueType());
memberType = member.GetMemberValueType();
}
return memberType;
}
/// <summary>This function supports all members: type, property, field, method</summary>
/// <param name="memberInfo">Type (or any memberInfo) that will be used to get result value. When not provided memberInfo will be taken from obj type</param>
/// <param name="obj">Object from which finding will be performed</param>
/// <param name="path">path from current member with dot "." as delimiter</param>
/// <param name="bindingFlags">Custom binding flags to use</param>
/// <returns>member's value from given path</returns>
/// <exception cref="MemberNotFoundException">When property at given path doesn't exist</exception>
public static object GetMemberValueByPath(this MemberInfo memberInfo, object obj, string path, BindingFlags bindingFlags = DefaultBindingFlags)
{
var pathParts = path.Split('.');
var memberType = memberInfo.GetMemberValueType();
var memberObj = obj;
foreach (var pathPart in pathParts)
{
var member = memberType.GetMember(pathPart, bindingFlags).FirstOrDefault();
if (member == null) throw new MemberNotFoundException(path, memberType.GetMemberValueType());
memberType = member.GetMemberValueType();
memberObj = memberType.GetMemberValue(memberObj);
}
return memberObj;
}
/// <inheritdoc cref="GetMemberValueByPath(MemberInfo, object, string, BindingFlags)"/>
/// <exception cref="MemberValueWrongTypeException">When result is not desired type</exception>
/// <typeparam name="T">Result type</typeparam>
public static T GetMemberValueByPath<T>(this MemberInfo memberInfo, object obj, string path, BindingFlags bindingFlags = DefaultBindingFlags)
{
var result = GetMemberValueByPath(memberInfo, obj, path, bindingFlags);
if (result is T typedResult) return typedResult;
throw new MemberValueWrongTypeException(path, obj.GetType(), typeof(T), result.GetType());
}
/// <inheritdoc cref="GetMemberValueByPath(MemberInfo, object, string, BindingFlags)"/>
public static object GetMemberValueByPath(object obj, string path, BindingFlags bindingFlags = DefaultBindingFlags)
{
return GetMemberValueByPath(obj.GetType(), obj, path, bindingFlags);
}
/// <summary>
/// Tries to find specific generic type in currently implementation.
/// </summary>
/// <param name="type">This type</param>
/// <param name="genericTypeDefinition">Generic type to find</param>
/// <returns>Found generic type with parameters</returns>
/// <exception cref="Exception">Generic type to find isn't generic.</exception>
public static Type FindGenericDefinitionType(this Type type, Type genericTypeDefinition)
{
if (!genericTypeDefinition.IsGenericTypeDefinition) throw new Exception("The generic argument must be a generic type definition.");
var tempType = type;
while (tempType != null && tempType != typeof(object))
{
if (tempType.IsGenericType && tempType.GetGenericTypeDefinition() == genericTypeDefinition) return tempType;
tempType = tempType.BaseType;
}
return null;
}
/// <summary>
/// Checks if specific generic type in currently implementation has type as argument at index.
/// </summary>
/// <param name="type">this type</param>
/// <param name="genericTypeDefinition">Generic type to find</param>
/// <param name="argIndex">index to check</param>
/// <param name="argType">type in generic implementation</param>
/// <returns></returns>
public static bool IsArgTypeInGenericDefinitionType(this Type type, Type genericTypeDefinition, int argIndex, Type argType)
{
return type.FindGenericDefinitionType(genericTypeDefinition).GenericTypeArguments.ElementAtOrDefault(argIndex) == argType;
}
/// <summary>
/// Checks if this member has public read accessor.
/// </summary>
/// <param name="memberInfo">this member</param>
/// <returns>TRUE if member exists and is public</returns>
public static bool IsMemberReadPublic(this MemberInfo memberInfo)
{
return memberInfo switch
{
Type type => type.IsPublic,
PropertyInfo pi => pi.GetGetMethod() != null,
MethodInfo mi => mi.IsPublic,
FieldInfo fi => fi.IsPublic,
_ => false
};
}
public static IEnumerable<Type> GetAllTypes(this Type forType, bool withObject = false)
{
var tempType = forType;
while (tempType is not null)
{
yield return tempType;
tempType = tempType.BaseType;
if (!withObject && tempType == typeof(object)) yield break;
}
}
}
}