summaryrefslogtreecommitdiff
path: root/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <github@jplamondonw.com>2016-12-09 14:15:14 -0500
committerJesse Plamondon-Willard <github@jplamondonw.com>2016-12-09 14:15:14 -0500
commit80b6e208418b7d9237bc4aa98c68d2bb849b49d5 (patch)
treea06c7646bf091fc4c6022c7d2b708ea4e4cf7e61 /src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs
parentcd0e5961d454e5861e2fd760388eb6920a1e2257 (diff)
downloadSMAPI-80b6e208418b7d9237bc4aa98c68d2bb849b49d5.tar.gz
SMAPI-80b6e208418b7d9237bc4aa98c68d2bb849b49d5.tar.bz2
SMAPI-80b6e208418b7d9237bc4aa98c68d2bb849b49d5.zip
cache reflection lookups with sliding expiry (#185)
Diffstat (limited to 'src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs')
-rw-r--r--src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs67
1 files changed, 55 insertions, 12 deletions
diff --git a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs
index 17758a39..fd916bbe 100644
--- a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs
+++ b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs
@@ -1,13 +1,26 @@
using System;
+using System.Linq;
using System.Reflection;
+using System.Runtime.Caching;
using StardewModdingAPI.Reflection;
namespace StardewModdingAPI.Framework.Reflection
{
/// <summary>Provides helper methods for accessing private game code.</summary>
+ /// <remarks>This implementation searches up the type hierarchy, and caches the reflected fields and methods with a sliding expiry (to optimise performance without unnecessary memory usage).</remarks>
internal class ReflectionHelper : IReflectionHelper
{
/*********
+ ** Properties
+ *********/
+ /// <summary>The cached fields and methods found via reflection.</summary>
+ private readonly MemoryCache Cache = new MemoryCache(typeof(ReflectionHelper).FullName);
+
+ /// <summary>The sliding cache expiration time.</summary>
+ private readonly TimeSpan SlidingCacheExpiry = TimeSpan.FromMinutes(5);
+
+
+ /*********
** Public methods
*********/
/****
@@ -152,12 +165,17 @@ namespace StardewModdingAPI.Framework.Reflection
/// <param name="bindingFlags">The reflection binding which flags which indicates what type of field to find.</param>
private IPrivateField<TValue> GetFieldFromHierarchy<TValue>(Type type, object obj, string name, BindingFlags bindingFlags)
{
- FieldInfo field = null;
- for (; type != null && field == null; type = type.BaseType)
- field = type.GetField(name, bindingFlags);
+ bool isStatic = bindingFlags.HasFlag(BindingFlags.Static);
+ FieldInfo field = this.GetCached<FieldInfo>($"field::{isStatic}::{type.FullName}::{name}", () =>
+ {
+ FieldInfo fieldInfo = null;
+ for (; type != null && fieldInfo == null; type = type.BaseType)
+ fieldInfo = type.GetField(name, bindingFlags);
+ return fieldInfo;
+ });
return field != null
- ? new PrivateField<TValue>(type, obj, field, isStatic: bindingFlags.HasFlag(BindingFlags.Static))
+ ? new PrivateField<TValue>(type, obj, field, isStatic)
: null;
}
@@ -168,9 +186,14 @@ namespace StardewModdingAPI.Framework.Reflection
/// <param name="bindingFlags">The reflection binding which flags which indicates what type of method to find.</param>
private IPrivateMethod GetMethodFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags)
{
- MethodInfo method = null;
- for (; type != null && method == null; type = type.BaseType)
- method = type.GetMethod(name, bindingFlags);
+ bool isStatic = bindingFlags.HasFlag(BindingFlags.Static);
+ MethodInfo method = this.GetCached($"method::{isStatic}::{type.FullName}::{name}", () =>
+ {
+ MethodInfo methodInfo = null;
+ for (; type != null && methodInfo == null; type = type.BaseType)
+ methodInfo = type.GetMethod(name, bindingFlags);
+ return methodInfo;
+ });
return method != null
? new PrivateMethod(type, obj, method, isStatic: bindingFlags.HasFlag(BindingFlags.Static))
@@ -185,13 +208,33 @@ namespace StardewModdingAPI.Framework.Reflection
/// <param name="argumentTypes">The argument types of the method signature to find.</param>
private PrivateMethod GetMethodFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags, Type[] argumentTypes)
{
- MethodInfo method = null;
- for (; type != null && method == null; type = type.BaseType)
- method = type.GetMethod(name, bindingFlags, null, argumentTypes, null);
-
+ bool isStatic = bindingFlags.HasFlag(BindingFlags.Static);
+ MethodInfo method = this.GetCached($"method::{isStatic}::{type.FullName}::{name}({string.Join(",", argumentTypes.Select(p => p.FullName))})", () =>
+ {
+ MethodInfo methodInfo = null;
+ for (; type != null && methodInfo == null; type = type.BaseType)
+ methodInfo = type.GetMethod(name, bindingFlags, null, argumentTypes, null);
+ return methodInfo;
+ });
return method != null
- ? new PrivateMethod(type, obj, method, isStatic: bindingFlags.HasFlag(BindingFlags.Static))
+ ? new PrivateMethod(type, obj, method, isStatic)
: null;
}
+
+ /// <summary>Get a method or field through the cache.</summary>
+ /// <typeparam name="TMemberInfo">The expected <see cref="MemberInfo"/> type.</typeparam>
+ /// <param name="key">The cache key.</param>
+ /// <param name="fetch">Fetches a new value to cache.</param>
+ private TMemberInfo GetCached<TMemberInfo>(string key, Func<TMemberInfo> fetch) where TMemberInfo : MemberInfo
+ {
+ // get from cache
+ if (this.Cache.Contains(key))
+ return (TMemberInfo)this.Cache[key];
+
+ // fetch & cache new value
+ TMemberInfo result = fetch();
+ this.Cache.Add(key, result, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry });
+ return result;
+ }
}
} \ No newline at end of file