mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-19 15:51:48 +00:00
improve reflection search
This commit is contained in:
6 files changed
+128
-17
No files matched your search
@@ -14,8 +14,13 @@ public sealed class ModdedOptionChangedAttribute(string? methodName = null, Type
|
|||||||
{
|
{
|
||||||
internal Delegate? GetChanged(AbstractOptionGroup group, PropertyInfo property, IModdedOption option)
|
internal Delegate? GetChanged(AbstractOptionGroup group, PropertyInfo property, IModdedOption option)
|
||||||
{
|
{
|
||||||
if (ResolveMethod(group, property, out var type, out var instanceGetter) is not { } method)
|
if (ResolveMethod(group, property, out var type, out var instanceGetter, out var notFound) is not { } method)
|
||||||
|
{
|
||||||
|
if (notFound)
|
||||||
|
Error($"Method {MemberName} does not exist in {type.FullName}");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var valueType = option.GetValueType();
|
var valueType = option.GetValueType();
|
||||||
|
|
||||||
@@ -28,8 +33,13 @@ public sealed class ModdedOptionChangedAttribute(string? methodName = null, Type
|
|||||||
|
|
||||||
internal Func<int, Delegate?>? GetListChanged(AbstractOptionGroup group, PropertyInfo property, Type valueType)
|
internal Func<int, Delegate?>? GetListChanged(AbstractOptionGroup group, PropertyInfo property, Type valueType)
|
||||||
{
|
{
|
||||||
if (ResolveMethod(group, property, out var type, out var instanceGetter) is not { } method)
|
if (ResolveMethod(group, property, out var type, out var instanceGetter, out var notFound) is not { } method)
|
||||||
|
{
|
||||||
|
if (notFound)
|
||||||
|
Error($"Method {MemberName} does not exist in {type.FullName}");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ValidateListSignature(method, typeof(void), out var hasIndex, valueType))
|
if (!ValidateListSignature(method, typeof(void), out var hasIndex, valueType))
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -14,8 +14,13 @@ public sealed class ModdedOptionGetterAttribute(string? methodName = null, Type?
|
|||||||
{
|
{
|
||||||
internal Delegate? GetGetter(AbstractOptionGroup group, PropertyInfo property, IModdedOption option)
|
internal Delegate? GetGetter(AbstractOptionGroup group, PropertyInfo property, IModdedOption option)
|
||||||
{
|
{
|
||||||
if (ResolveMethod(group, property, out var type, out var instanceGetter) is not { } method)
|
if (ResolveMethod(group, property, out var type, out var instanceGetter, out var notFound) is not { } method)
|
||||||
|
{
|
||||||
|
if (notFound)
|
||||||
|
Error($"Method {MemberName} does not exist in {type.FullName}");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var valueType = option.GetValueType();
|
var valueType = option.GetValueType();
|
||||||
|
|
||||||
@@ -28,8 +33,13 @@ public sealed class ModdedOptionGetterAttribute(string? methodName = null, Type?
|
|||||||
|
|
||||||
internal Func<int, Delegate?>? GetListGetter(AbstractOptionGroup group, PropertyInfo property, Type valueType)
|
internal Func<int, Delegate?>? GetListGetter(AbstractOptionGroup group, PropertyInfo property, Type valueType)
|
||||||
{
|
{
|
||||||
if (ResolveMethod(group, property, out var type, out var instanceGetter) is not { } method)
|
if (ResolveMethod(group, property, out var type, out var instanceGetter, out var notFound) is not { } method)
|
||||||
|
{
|
||||||
|
if (notFound)
|
||||||
|
Error($"Method {MemberName} does not exist in {type.FullName}");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ValidateListSignature(method, valueType, out var hasIndex, valueType))
|
if (!ValidateListSignature(method, valueType, out var hasIndex, valueType))
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Reflection.Emit;
|
using System.Reflection.Emit;
|
||||||
|
using MiraAPI.Utilities;
|
||||||
|
|
||||||
namespace MiraAPI.GameOptions.Attributes;
|
namespace MiraAPI.GameOptions.Attributes;
|
||||||
|
|
||||||
@@ -127,7 +128,7 @@ public abstract class ModdedOptionMemberAttribute(string? memberName, Type? hold
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolves the target <see cref="MemberInfo"/> (property or method) and its associated singleton instance getter, if applicable.
|
/// Resolves the target <see cref="MemberInfo"/> (property, field or method) and its associated singleton instance getter, if applicable.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="group">The option group instance the property belongs to.</param>
|
/// <param name="group">The option group instance the property belongs to.</param>
|
||||||
/// <param name="property">The option property decorated with this attribute.</param>
|
/// <param name="property">The option property decorated with this attribute.</param>
|
||||||
@@ -136,16 +137,19 @@ public abstract class ModdedOptionMemberAttribute(string? memberName, Type? hold
|
|||||||
/// When this method returns, contains the <see cref="OptionGroupSingleton{T}.Instance"/> getter <see cref="MethodInfo"/>
|
/// When this method returns, contains the <see cref="OptionGroupSingleton{T}.Instance"/> getter <see cref="MethodInfo"/>
|
||||||
/// if the resolved member is an instance member on an <see cref="AbstractOptionGroup"/>; otherwise, <see langword="null"/>.
|
/// if the resolved member is an instance member on an <see cref="AbstractOptionGroup"/>; otherwise, <see langword="null"/>.
|
||||||
/// </param>
|
/// </param>
|
||||||
|
/// <param name="notFound">When this method returns, contains a flag that indicates if the member was not found, as null can be returned from other circumstances too.</param>
|
||||||
/// <returns>The resolved <see cref="MemberInfo"/> if found; otherwise, <see langword="null"/>.</returns>
|
/// <returns>The resolved <see cref="MemberInfo"/> if found; otherwise, <see langword="null"/>.</returns>
|
||||||
[SuppressMessage("Major Code Smell", "S3011:Reflection should not be used to increase accessibility of classes, methods, or fields", Justification = "It is safe.")]
|
[SuppressMessage("Major Code Smell", "S3011:Reflection should not be used to increase accessibility of classes, methods, or fields", Justification = "It is safe.")]
|
||||||
protected MemberInfo? ResolveMember(AbstractOptionGroup group, PropertyInfo property, out Type type, out MethodInfo? instanceGetter)
|
protected MemberInfo? ResolveMember(AbstractOptionGroup group, PropertyInfo property, out Type type, out MethodInfo? instanceGetter, out bool notFound)
|
||||||
{
|
{
|
||||||
var groupType = group.GetType();
|
var groupType = group.GetType();
|
||||||
var flags = BindingFlags.Static | BindingFlags.Public;
|
var flags = BindingFlags.Static | BindingFlags.Public;
|
||||||
|
|
||||||
type = holderType ?? groupType;
|
|
||||||
MemberName ??= property.Name + defaultSuffix;
|
MemberName ??= property.Name + defaultSuffix;
|
||||||
|
|
||||||
|
type = holderType ?? groupType;
|
||||||
|
instanceGetter = null;
|
||||||
|
|
||||||
if (type.IsAssignableTo(typeof(AbstractOptionGroup)))
|
if (type.IsAssignableTo(typeof(AbstractOptionGroup)))
|
||||||
{
|
{
|
||||||
flags |= BindingFlags.Instance;
|
flags |= BindingFlags.Instance;
|
||||||
@@ -158,12 +162,69 @@ public abstract class ModdedOptionMemberAttribute(string? memberName, Type? hold
|
|||||||
.GetProperty(nameof(OptionGroupSingleton<>.Instance))!
|
.GetProperty(nameof(OptionGroupSingleton<>.Instance))!
|
||||||
.GetGetMethod();
|
.GetGetMethod();
|
||||||
}
|
}
|
||||||
else
|
else if (!type.IsStatic())
|
||||||
{
|
{
|
||||||
instanceGetter = null;
|
flags |= BindingFlags.Instance;
|
||||||
|
|
||||||
|
var canSearchPrivates = type.GetCustomAttribute<SearchPrivateAttribute>() != null;
|
||||||
|
|
||||||
|
if (canSearchPrivates)
|
||||||
|
flags |= BindingFlags.NonPublic;
|
||||||
|
|
||||||
|
var getterFlags = BindingFlags.Static | BindingFlags.Public | (canSearchPrivates ? BindingFlags.NonPublic : BindingFlags.Default);
|
||||||
|
|
||||||
|
foreach (var method in type.GetMethods(getterFlags))
|
||||||
|
{
|
||||||
|
if (method.GetParameters().Length > 0 || (method.GetCustomAttribute<InstanceGetterAttribute>() == null && method.Name != "GetInstance"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
instanceGetter = method;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instanceGetter == null)
|
||||||
|
{
|
||||||
|
foreach (var prop in type.GetProperties(getterFlags))
|
||||||
|
{
|
||||||
|
if (prop.GetCustomAttribute<InstanceGetterAttribute>() == null && prop.Name != "Instance")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var getMethod = prop.GetGetMethod(canSearchPrivates);
|
||||||
|
|
||||||
|
if (getMethod == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
instanceGetter = getMethod;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instanceGetter == null)
|
||||||
|
{
|
||||||
|
Error($"No valid static instance getter (method GetInstance or property Instance) found in instanced type {type.Name}. Try renaming the method/property, or decorate it with InstanceGetterAttribute.");
|
||||||
|
notFound = false;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!instanceGetter.ReturnType.IsAssignableTo(type))
|
||||||
|
{
|
||||||
|
Error($"Found instance getter {instanceGetter.Name}, but the return type did not match what was expected ({type.Name}).");
|
||||||
|
notFound = false;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type.GetCustomAttribute<SearchPrivateAttribute>() != null)
|
||||||
|
{
|
||||||
|
flags |= BindingFlags.NonPublic;
|
||||||
}
|
}
|
||||||
|
|
||||||
var member = type.GetMember(MemberName, flags).FirstOrDefault();
|
var member = type.GetMember(MemberName, flags).FirstOrDefault();
|
||||||
|
notFound = true;
|
||||||
|
|
||||||
|
if (member == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
notFound = false;
|
||||||
|
|
||||||
if (instanceGetter == null)
|
if (instanceGetter == null)
|
||||||
return member;
|
return member;
|
||||||
@@ -196,10 +257,11 @@ public abstract class ModdedOptionMemberAttribute(string? memberName, Type? hold
|
|||||||
/// When this method returns, contains the <see cref="OptionGroupSingleton{T}.Instance"/> getter <see cref="MethodInfo"/>
|
/// When this method returns, contains the <see cref="OptionGroupSingleton{T}.Instance"/> getter <see cref="MethodInfo"/>
|
||||||
/// if the resolved method is an instance method on an <see cref="AbstractOptionGroup"/>; otherwise, <see langword="null"/>.
|
/// if the resolved method is an instance method on an <see cref="AbstractOptionGroup"/>; otherwise, <see langword="null"/>.
|
||||||
/// </param>
|
/// </param>
|
||||||
|
/// <param name="notFound">When this method returns, contains a flag that indicates if the member was not found, as null can be returned from other circumstances too.</param>
|
||||||
/// <returns>The resolved <see cref="MethodInfo"/> if found; otherwise, <see langword="null"/>.</returns>
|
/// <returns>The resolved <see cref="MethodInfo"/> if found; otherwise, <see langword="null"/>.</returns>
|
||||||
protected MethodInfo? ResolveMethod(AbstractOptionGroup group, PropertyInfo property, out Type type, out MethodInfo? instanceGetter)
|
protected MethodInfo? ResolveMethod(AbstractOptionGroup group, PropertyInfo property, out Type type, out MethodInfo? instanceGetter, out bool notFound)
|
||||||
{
|
{
|
||||||
return ResolveMember(group, property, out type, out instanceGetter) as MethodInfo;
|
return ResolveMember(group, property, out type, out instanceGetter, out notFound) as MethodInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -14,8 +14,13 @@ public sealed class ModdedOptionSetterAttribute(string? methodName = null, Type?
|
|||||||
{
|
{
|
||||||
internal Delegate? GetSetter(AbstractOptionGroup group, PropertyInfo property, IModdedOption option)
|
internal Delegate? GetSetter(AbstractOptionGroup group, PropertyInfo property, IModdedOption option)
|
||||||
{
|
{
|
||||||
if (ResolveMethod(group, property, out var type, out var instanceGetter) is not { } method)
|
if (ResolveMethod(group, property, out var type, out var instanceGetter, out var notFound) is not { } method)
|
||||||
|
{
|
||||||
|
if (notFound)
|
||||||
|
Error($"Method {MemberName} does not exist in {type.FullName}");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var valueType = option.GetValueType();
|
var valueType = option.GetValueType();
|
||||||
|
|
||||||
@@ -28,8 +33,13 @@ public sealed class ModdedOptionSetterAttribute(string? methodName = null, Type?
|
|||||||
|
|
||||||
internal Func<int, Delegate?>? GetListSetter(AbstractOptionGroup group, PropertyInfo property, Type valueType)
|
internal Func<int, Delegate?>? GetListSetter(AbstractOptionGroup group, PropertyInfo property, Type valueType)
|
||||||
{
|
{
|
||||||
if (ResolveMethod(group, property, out var type, out var instanceGetter) is not { } method)
|
if (ResolveMethod(group, property, out var type, out var instanceGetter, out var notFound) is not { } method)
|
||||||
|
{
|
||||||
|
if (notFound)
|
||||||
|
Error($"Method {MemberName} does not exist in {type.FullName}");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ValidateListSignature(method, valueType, out var hasIndex, valueType, valueType))
|
if (!ValidateListSignature(method, valueType, out var hasIndex, valueType, valueType))
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -20,9 +20,11 @@ public sealed class ModdedOptionVisibilityAttribute(string? memberName = null, T
|
|||||||
|
|
||||||
internal Func<bool>? GetVisibility(AbstractOptionGroup group, PropertyInfo property)
|
internal Func<bool>? GetVisibility(AbstractOptionGroup group, PropertyInfo property)
|
||||||
{
|
{
|
||||||
if (ResolveMember(group, property, out Type type, out var instanceGetter) is not { } member)
|
if (ResolveMember(group, property, out Type type, out var instanceGetter, out var notFound) is not { } member)
|
||||||
{
|
{
|
||||||
Error($"Member {MemberName} does not exist in {type.FullName}");
|
if (notFound)
|
||||||
|
Error($"Member {MemberName} does not exist in {type.FullName}");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,9 +109,11 @@ public sealed class ModdedOptionVisibilityAttribute(string? memberName = null, T
|
|||||||
|
|
||||||
internal Func<int, Delegate?>? GetListVisibility(AbstractOptionGroup group, PropertyInfo property)
|
internal Func<int, Delegate?>? GetListVisibility(AbstractOptionGroup group, PropertyInfo property)
|
||||||
{
|
{
|
||||||
if (ResolveMember(group, property, out Type type, out var instanceGetter) is not { } member)
|
if (ResolveMember(group, property, out Type type, out var instanceGetter, out var notFound) is not { } member)
|
||||||
{
|
{
|
||||||
Error($"Member {MemberName} does not exist in {type.FullName}");
|
if (notFound)
|
||||||
|
Error($"Member {MemberName} does not exist in {type.FullName}");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MiraAPI.GameOptions.Attributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An attribute used to mark a method or property as one that returns an instance of the class it's declared in.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)]
|
||||||
|
public sealed class InstanceGetterAttribute : Attribute;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An attribute used to mark a class as one whose reflection search should include private members.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public sealed class SearchPrivateAttribute : Attribute;
|
||||||
Reference in new issue
Block a user