From cd93d59eaf5aa05e0fa55eadd958339b9042155d Mon Sep 17 00:00:00 2001 From: Tyler Staples Date: Mon, 12 Dec 2016 15:58:44 -0800 Subject: Added a struct to wrap cache entries for the sake of tracking invalid lookups. This fixes the issue where a null reference exception would be thrown when trying to look up non-existant or non-private members. Added a null check to GetPrivateValue and it's overloads to fix the issue where it would throw a null reference exception when required was false and the field was null. --- .../Framework/Reflection/ReflectionHelper.cs | 49 ++++++++++++++++++---- 1 file changed, 42 insertions(+), 7 deletions(-) (limited to 'src/StardewModdingAPI/Framework/Reflection') diff --git a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs index 38b4e357..1d5cf157 100644 --- a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs +++ b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs @@ -12,6 +12,16 @@ namespace StardewModdingAPI.Framework.Reflection /********* ** Properties *********/ + /// MemberInfo wrapper for tracking validity. + internal struct CacheEntry + { + /// Is this member valid. Used to avoid unecessary lookups. + public bool IsValid; + + /// The reflection data for this member. This will be null if IsValid is false. + public MemberInfo MemberInfo; + } + /// The cached fields and methods found via reflection. private readonly MemoryCache Cache = new MemoryCache(typeof(ReflectionHelper).FullName); @@ -67,10 +77,17 @@ namespace StardewModdingAPI.Framework.Reflection /// The object which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. - /// This is a shortcut for followed by . + /// The value of the field or the default value of the type if the field is not found. + /// + /// This is a shortcut for followed by . + /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. + /// public TValue GetPrivateValue(object obj, string name, bool required = true) { - return this.GetPrivateField(obj, name, required).GetValue(); + IPrivateField field = this.GetPrivateField(obj, name, required); + return (field != null) + ? field.GetValue() + : default(TValue); } /// Get the value of a private static field. @@ -78,10 +95,17 @@ namespace StardewModdingAPI.Framework.Reflection /// The type which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. - /// This is a shortcut for followed by . + /// The value of the field or the default value of the type if the field is not found. + /// + /// This is a shortcut for followed by . + /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. + /// public TValue GetPrivateValue(Type type, string name, bool required = true) { - return this.GetPrivateField(type, name, required).GetValue(); + IPrivateField field = this.GetPrivateField(type, name, required); + return (field != null) + ? field.GetValue() + : default(TValue); } /**** @@ -228,11 +252,22 @@ namespace StardewModdingAPI.Framework.Reflection { // get from cache if (this.Cache.Contains(key)) - return (TMemberInfo)this.Cache[key]; + { + CacheEntry entry = (CacheEntry)this.Cache[key]; + return entry.IsValid + ? (TMemberInfo)entry.MemberInfo + : default(TMemberInfo); + } - // fetch & cache new value + // fetch & cache new value, marking if it's valid for future lookups. TMemberInfo result = fetch(); - this.Cache.Add(key, result, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry }); + CacheEntry cacheEntry = new CacheEntry() + { + IsValid = (result != null), + MemberInfo = result + }; + + this.Cache.Add(key, cacheEntry, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry }); return result; } } -- cgit From d9e87399bf65d0053ad57d316d0df9e1a631a42b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Dec 2016 12:27:44 -0500 Subject: format code (#193) --- .../Framework/Reflection/CacheEntry.cs | 30 ++++++++++++++++++++ .../Framework/Reflection/ReflectionHelper.cs | 33 ++++++---------------- src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 3 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/Reflection/CacheEntry.cs (limited to 'src/StardewModdingAPI/Framework/Reflection') diff --git a/src/StardewModdingAPI/Framework/Reflection/CacheEntry.cs b/src/StardewModdingAPI/Framework/Reflection/CacheEntry.cs new file mode 100644 index 00000000..30faca37 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Reflection/CacheEntry.cs @@ -0,0 +1,30 @@ +using System.Reflection; + +namespace StardewModdingAPI.Framework.Reflection +{ + /// A cached member reflection result. + internal struct CacheEntry + { + /********* + ** Accessors + *********/ + /// Whether the lookup found a valid match. + public bool IsValid; + + /// The reflection data for this member (or null if invalid). + public MemberInfo MemberInfo; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Whether the lookup found a valid match. + /// The reflection data for this member (or null if invalid). + public CacheEntry(bool isValid, MemberInfo memberInfo) + { + this.IsValid = isValid; + this.MemberInfo = memberInfo; + } + } +} diff --git a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs index 1d5cf157..edf59b81 100644 --- a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs +++ b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs @@ -12,16 +12,6 @@ namespace StardewModdingAPI.Framework.Reflection /********* ** Properties *********/ - /// MemberInfo wrapper for tracking validity. - internal struct CacheEntry - { - /// Is this member valid. Used to avoid unecessary lookups. - public bool IsValid; - - /// The reflection data for this member. This will be null if IsValid is false. - public MemberInfo MemberInfo; - } - /// The cached fields and methods found via reflection. private readonly MemoryCache Cache = new MemoryCache(typeof(ReflectionHelper).FullName); @@ -77,7 +67,7 @@ namespace StardewModdingAPI.Framework.Reflection /// The object which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. - /// The value of the field or the default value of the type if the field is not found. + /// Returns the field value, or the default value for if the field wasn't found and is false. /// /// This is a shortcut for followed by . /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. @@ -85,7 +75,7 @@ namespace StardewModdingAPI.Framework.Reflection public TValue GetPrivateValue(object obj, string name, bool required = true) { IPrivateField field = this.GetPrivateField(obj, name, required); - return (field != null) + return field != null ? field.GetValue() : default(TValue); } @@ -95,7 +85,7 @@ namespace StardewModdingAPI.Framework.Reflection /// The type which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. - /// The value of the field or the default value of the type if the field is not found. + /// Returns the field value, or the default value for if the field wasn't found and is false. /// /// This is a shortcut for followed by . /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. @@ -103,7 +93,7 @@ namespace StardewModdingAPI.Framework.Reflection public TValue GetPrivateValue(Type type, string name, bool required = true) { IPrivateField field = this.GetPrivateField(type, name, required); - return (field != null) + return field != null ? field.GetValue() : default(TValue); } @@ -254,21 +244,16 @@ namespace StardewModdingAPI.Framework.Reflection if (this.Cache.Contains(key)) { CacheEntry entry = (CacheEntry)this.Cache[key]; - return entry.IsValid - ? (TMemberInfo)entry.MemberInfo + return entry.IsValid + ? (TMemberInfo)entry.MemberInfo : default(TMemberInfo); } - // fetch & cache new value, marking if it's valid for future lookups. + // fetch & cache new value TMemberInfo result = fetch(); - CacheEntry cacheEntry = new CacheEntry() - { - IsValid = (result != null), - MemberInfo = result - }; - + CacheEntry cacheEntry = new CacheEntry(result != null, result); this.Cache.Add(key, cacheEntry, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry }); return result; } } -} \ No newline at end of file +} diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 1a31b751..65083e67 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -162,6 +162,7 @@ + -- cgit