cache reflection lookups with sliding expiry (#185)

This commit is contained in:
Jesse Plamondon-Willard 2016-12-09 14:15:14 -05:00
parent cd0e5961d4
commit 80b6e20841
2 changed files with 56 additions and 12 deletions

View File

@ -1,12 +1,25 @@
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;
}
}
}

View File

@ -106,6 +106,7 @@
<Reference Include="System.Numerics">
<Private>True</Private>
</Reference>
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Windows.Forms" Condition="$(OS) == 'Windows_NT'" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />