Skip to content

Commit a390e14

Browse files
committed
Improves performance of TypeByName(), adds TypeSearch()
1 parent 267311e commit a390e14

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

Harmony/Tools/AccessTools.cs

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Linq;
99
using System.Reflection;
1010
using System.Reflection.Emit;
11+
using System.Text.RegularExpressions;
1112
using System.Threading;
1213
#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
1314
using System.Runtime.CompilerServices;
@@ -25,6 +26,8 @@ namespace HarmonyLib
2526
///
2627
public static class AccessTools
2728
{
29+
private static Type[] allTypesCached = null;
30+
2831
/// <summary>Shortcut for <see cref="BindingFlags"/> to simplify the use of reflections and make it work for any access level</summary>
2932
///
3033
public static readonly BindingFlags all = BindingFlags.Public // This should a be const, but changing from static (readonly) to const breaks binary compatibility.
@@ -51,13 +54,57 @@ public static class AccessTools
5154
///
5255
public static Type TypeByName(string name)
5356
{
54-
var type = Type.GetType(name, false);
55-
type ??= AllTypes().FirstOrDefault(t => t.FullName == name);
56-
type ??= AllTypes().FirstOrDefault(t => t.Name == name);
57-
if (type is null) FileLog.Debug($"AccessTools.TypeByName: Could not find type named {name}");
58-
return type;
57+
var localType = Type.GetType(name, false);
58+
if (localType is not null)
59+
return localType;
60+
61+
foreach (var assembly in AllAssemblies())
62+
{
63+
var specificType = assembly.GetType(name, false);
64+
if (specificType is not null)
65+
return specificType;
66+
}
67+
68+
var allTypes = AllTypes().ToArray();
69+
70+
var fullType = allTypes.FirstOrDefault(t => t.FullName == name);
71+
if (fullType is not null)
72+
return fullType;
73+
74+
var partialType = allTypes.FirstOrDefault(t => t.Name == name);
75+
if (partialType is not null)
76+
return partialType;
77+
78+
FileLog.Debug($"AccessTools.TypeByName: Could not find type named {name}");
79+
return null;
5980
}
6081

82+
/// <summary>Searches a type by regular expression. For exact searching, use <see cref="AccessTools.TypeByName(string)"/>.</summary>
83+
/// <param name="search">The regular expression that matches against Type.FullName or Type.Name</param>
84+
/// <param name="invalidateCache">Refetches the cached types if set to true</param>
85+
/// <returns>The first type where FullName or Name matches the search</returns>
86+
///
87+
public static Type TypeSearch(Regex search, bool invalidateCache = false)
88+
{
89+
if (allTypesCached == null || invalidateCache)
90+
allTypesCached = [.. AllTypes()];
91+
92+
var fullType = allTypesCached.FirstOrDefault(t => search.IsMatch(t.FullName));
93+
if (fullType is not null)
94+
return fullType;
95+
96+
var partialType = allTypesCached.FirstOrDefault(t => search.IsMatch(t.Name));
97+
if (partialType is not null)
98+
return partialType;
99+
100+
FileLog.Debug($"AccessTools.TypeSearch: Could not find type with regular expression {search}");
101+
return null;
102+
}
103+
104+
/// <summary>Clears the type cache that <see cref="AccessTools.TypeSearch(Regex, bool)"/> uses.</summary>
105+
///
106+
public static void ClearTypeSearchCache() => allTypesCached = [];
107+
61108
/// <summary>Gets all successfully loaded types from a given assembly</summary>
62109
/// <param name="assembly">The assembly</param>
63110
/// <returns>An array of types</returns>

HarmonyTests/GlobalSuppressions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@
3030
[assembly: SuppressMessage("Code Quality", "IDE0051")]
3131
[assembly: SuppressMessage("Interoperability", "SYSLIB1054")]
3232
[assembly: SuppressMessage("Obsoletions", "SYSLIB0014")]
33+
[assembly: SuppressMessage("", "SYSLIB1045")]

HarmonyTests/Tools/TestAccessTools.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.IO;
77
using System.Reflection;
88
using System.Reflection.Emit;
9+
using System.Text.RegularExpressions;
910
using static HarmonyLibTests.Assets.AccessToolsMethodDelegate;
1011
#if NETCOREAPP
1112
using System.Linq;
@@ -100,6 +101,14 @@ public void Test_AccessTools_TypeByName_CurrentAssemblies()
100101
Assert.Null(AccessTools.TypeByName("IAmALittleTeaPot.ShortAndStout"));
101102
}
102103

104+
[Test, NonParallelizable]
105+
public void Test_AccessTools_TypeSearch_CurrentAssemblies()
106+
{
107+
Assert.NotNull(AccessTools.TypeSearch(new Regex("^HarmonyLib\\.Harmony$")), "Harmony");
108+
Assert.NotNull(AccessTools.TypeSearch(new Regex(".+\\.Test_.+Tools$")), "Test_AccessTools");
109+
Assert.NotNull(AccessTools.TypeSearch(new Regex("harmony.+?tests\\..+environment", RegexOptions.IgnoreCase)), "HarmonyLibTests.TestEnvironment");
110+
}
111+
103112
[Test, NonParallelizable]
104113
public void Test_AccessTools_TypeByName_InvalidAssembly()
105114
{

0 commit comments

Comments
 (0)