diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-04-13 21:07:43 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-04-13 21:07:43 -0400 |
commit | 4adf8611131a5d86b15f017a42a0366837d14528 (patch) | |
tree | 879ba8dfc546382c3795e3d13a623b0e17f96469 /src/SMAPI/Framework/Reflection/Reflector.cs | |
parent | 5b24fff4771dd11c627ae20c827599fe37fa89ad (diff) | |
download | SMAPI-4adf8611131a5d86b15f017a42a0366837d14528.tar.gz SMAPI-4adf8611131a5d86b15f017a42a0366837d14528.tar.bz2 SMAPI-4adf8611131a5d86b15f017a42a0366837d14528.zip |
enable nullable annotations in the rest of SMAPI core (#837)
Diffstat (limited to 'src/SMAPI/Framework/Reflection/Reflector.cs')
-rw-r--r-- | src/SMAPI/Framework/Reflection/Reflector.cs | 127 |
1 files changed, 79 insertions, 48 deletions
diff --git a/src/SMAPI/Framework/Reflection/Reflector.cs b/src/SMAPI/Framework/Reflection/Reflector.cs index c6fc2c79..3490ee97 100644 --- a/src/SMAPI/Framework/Reflection/Reflector.cs +++ b/src/SMAPI/Framework/Reflection/Reflector.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Reflection; using System.Runtime.Caching; @@ -14,7 +12,7 @@ namespace StardewModdingAPI.Framework.Reflection ** Fields *********/ /// <summary>The cached fields and methods found via reflection.</summary> - private readonly MemoryCache Cache = new(typeof(Reflector).FullName); + private readonly MemoryCache Cache = new(typeof(Reflector).FullName!); /// <summary>The sliding cache expiration time.</summary> private readonly TimeSpan SlidingCacheExpiry = TimeSpan.FromMinutes(5); @@ -30,8 +28,9 @@ namespace StardewModdingAPI.Framework.Reflection /// <typeparam name="TValue">The field type.</typeparam> /// <param name="obj">The object which has the field.</param> /// <param name="name">The field name.</param> - /// <param name="required">Whether to throw an exception if the field is not found.</param> - /// <returns>Returns the field wrapper, or <c>null</c> if the field doesn't exist and <paramref name="required"/> is <c>false</c>.</returns> + /// <param name="required">Whether to throw an exception if the field isn't found. <strong>Due to limitations with nullable reference types, setting this to <c>false</c> will still mark the value non-nullable.</strong></param> + /// <returns>Returns the field wrapper, or <c>null</c> if <paramref name="required"/> is <c>false</c> and the field doesn't exist.</returns> + /// <exception cref="InvalidOperationException">The target field doesn't exist, and <paramref name="required"/> is true.</exception> public IReflectedField<TValue> GetField<TValue>(object obj, string name, bool required = true) { // validate @@ -39,24 +38,26 @@ namespace StardewModdingAPI.Framework.Reflection throw new ArgumentNullException(nameof(obj), "Can't get a instance field from a null object."); // get field from hierarchy - IReflectedField<TValue> field = this.GetFieldFromHierarchy<TValue>(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + IReflectedField<TValue>? field = this.GetFieldFromHierarchy<TValue>(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (required && field == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a '{name}' instance field."); - return field; + return field!; } /// <summary>Get a static field.</summary> /// <typeparam name="TValue">The field type.</typeparam> /// <param name="type">The type which has the field.</param> /// <param name="name">The field name.</param> - /// <param name="required">Whether to throw an exception if the field is not found.</param> + /// <param name="required">Whether to throw an exception if the field isn't found. <strong>Due to limitations with nullable reference types, setting this to <c>false</c> will still mark the value non-nullable.</strong></param> + /// <returns>Returns the field wrapper, or <c>null</c> if <paramref name="required"/> is <c>false</c> and the field doesn't exist.</returns> + /// <exception cref="InvalidOperationException">The target field doesn't exist, and <paramref name="required"/> is true.</exception> public IReflectedField<TValue> GetField<TValue>(Type type, string name, bool required = true) { // get field from hierarchy - IReflectedField<TValue> field = this.GetFieldFromHierarchy<TValue>(type, null, name, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public); + IReflectedField<TValue>? field = this.GetFieldFromHierarchy<TValue>(type, null, name, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public); if (required && field == null) throw new InvalidOperationException($"The {type.FullName} object doesn't have a '{name}' static field."); - return field; + return field!; } /**** @@ -66,7 +67,9 @@ namespace StardewModdingAPI.Framework.Reflection /// <typeparam name="TValue">The property type.</typeparam> /// <param name="obj">The object which has the property.</param> /// <param name="name">The property name.</param> - /// <param name="required">Whether to throw an exception if the property is not found.</param> + /// <param name="required">Whether to throw an exception if the property isn't found. <strong>Due to limitations with nullable reference types, setting this to <c>false</c> will still mark the value non-nullable.</strong></param> + /// <returns>Returns the property wrapper, or <c>null</c> if <paramref name="required"/> is <c>false</c> and the property doesn't exist.</returns> + /// <exception cref="InvalidOperationException">The target property doesn't exist, and <paramref name="required"/> is true.</exception> public IReflectedProperty<TValue> GetProperty<TValue>(object obj, string name, bool required = true) { // validate @@ -74,24 +77,26 @@ namespace StardewModdingAPI.Framework.Reflection throw new ArgumentNullException(nameof(obj), "Can't get a instance property from a null object."); // get property from hierarchy - IReflectedProperty<TValue> property = this.GetPropertyFromHierarchy<TValue>(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + IReflectedProperty<TValue>? property = this.GetPropertyFromHierarchy<TValue>(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (required && property == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a '{name}' instance property."); - return property; + return property!; } /// <summary>Get a static property.</summary> /// <typeparam name="TValue">The property type.</typeparam> /// <param name="type">The type which has the property.</param> /// <param name="name">The property name.</param> - /// <param name="required">Whether to throw an exception if the property is not found.</param> + /// <param name="required">Whether to throw an exception if the property isn't found. <strong>Due to limitations with nullable reference types, setting this to <c>false</c> will still mark the value non-nullable.</strong></param> + /// <returns>Returns the property wrapper, or <c>null</c> if <paramref name="required"/> is <c>false</c> and the property doesn't exist.</returns> + /// <exception cref="InvalidOperationException">The target property doesn't exist, and <paramref name="required"/> is true.</exception> public IReflectedProperty<TValue> GetProperty<TValue>(Type type, string name, bool required = true) { // get field from hierarchy - IReflectedProperty<TValue> property = this.GetPropertyFromHierarchy<TValue>(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + IReflectedProperty<TValue>? property = this.GetPropertyFromHierarchy<TValue>(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); if (required && property == null) throw new InvalidOperationException($"The {type.FullName} object doesn't have a '{name}' static property."); - return property; + return property!; } /**** @@ -99,8 +104,10 @@ namespace StardewModdingAPI.Framework.Reflection ****/ /// <summary>Get a instance method.</summary> /// <param name="obj">The object which has the method.</param> - /// <param name="name">The field name.</param> - /// <param name="required">Whether to throw an exception if the field is not found.</param> + /// <param name="name">The method name.</param> + /// <param name="required">Whether to throw an exception if the method isn't found. <strong>Due to limitations with nullable reference types, setting this to <c>false</c> will still mark the value non-nullable.</strong></param> + /// <returns>Returns the method wrapper, or <c>null</c> if <paramref name="required"/> is <c>false</c> and the method doesn't exist.</returns> + /// <exception cref="InvalidOperationException">The target method doesn't exist, and <paramref name="required"/> is true.</exception> public IReflectedMethod GetMethod(object obj, string name, bool required = true) { // validate @@ -108,23 +115,25 @@ namespace StardewModdingAPI.Framework.Reflection throw new ArgumentNullException(nameof(obj), "Can't get a instance method from a null object."); // get method from hierarchy - IReflectedMethod method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + IReflectedMethod? method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (required && method == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a '{name}' instance method."); - return method; + return method!; } /// <summary>Get a static method.</summary> /// <param name="type">The type which has the method.</param> - /// <param name="name">The field name.</param> - /// <param name="required">Whether to throw an exception if the field is not found.</param> + /// <param name="name">The method name.</param> + /// <param name="required">Whether to throw an exception if the method isn't found. <strong>Due to limitations with nullable reference types, setting this to <c>false</c> will still mark the value non-nullable.</strong></param> + /// <returns>Returns the method wrapper, or <c>null</c> if <paramref name="required"/> is <c>false</c> and the method doesn't exist.</returns> + /// <exception cref="InvalidOperationException">The target method doesn't exist, and <paramref name="required"/> is true.</exception> public IReflectedMethod GetMethod(Type type, string name, bool required = true) { // get method from hierarchy - IReflectedMethod method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + IReflectedMethod? method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); if (required && method == null) throw new InvalidOperationException($"The {type.FullName} object doesn't have a '{name}' static method."); - return method; + return method!; } @@ -134,18 +143,25 @@ namespace StardewModdingAPI.Framework.Reflection /// <summary>Get a field from the type hierarchy.</summary> /// <typeparam name="TValue">The expected field type.</typeparam> /// <param name="type">The type which has the field.</param> - /// <param name="obj">The object which has the field.</param> + /// <param name="obj">The object which has the field, or <c>null</c> for a static field.</param> /// <param name="name">The field name.</param> /// <param name="bindingFlags">The reflection binding which flags which indicates what type of field to find.</param> - private IReflectedField<TValue> GetFieldFromHierarchy<TValue>(Type type, object obj, string name, BindingFlags bindingFlags) + private IReflectedField<TValue>? GetFieldFromHierarchy<TValue>(Type type, object? obj, string name, BindingFlags bindingFlags) { bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); - FieldInfo field = this.GetCached<FieldInfo>($"field::{isStatic}::{type.FullName}::{name}", () => + FieldInfo? field = this.GetCached($"field::{isStatic}::{type.FullName}::{name}", () => { - FieldInfo fieldInfo = null; - for (; type != null && fieldInfo == null; type = type.BaseType) - fieldInfo = type.GetField(name, bindingFlags); - return fieldInfo; + for (Type? curType = type; curType != null; curType = curType.BaseType) + { + FieldInfo? fieldInfo = type.GetField(name, bindingFlags); + if (fieldInfo != null) + { + type = curType; + return fieldInfo; + } + } + + return null; }); return field != null @@ -156,18 +172,25 @@ namespace StardewModdingAPI.Framework.Reflection /// <summary>Get a property from the type hierarchy.</summary> /// <typeparam name="TValue">The expected property type.</typeparam> /// <param name="type">The type which has the property.</param> - /// <param name="obj">The object which has the property.</param> + /// <param name="obj">The object which has the property, or <c>null</c> for a static property.</param> /// <param name="name">The property name.</param> /// <param name="bindingFlags">The reflection binding which flags which indicates what type of property to find.</param> - private IReflectedProperty<TValue> GetPropertyFromHierarchy<TValue>(Type type, object obj, string name, BindingFlags bindingFlags) + private IReflectedProperty<TValue>? GetPropertyFromHierarchy<TValue>(Type type, object? obj, string name, BindingFlags bindingFlags) { bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); - PropertyInfo property = this.GetCached<PropertyInfo>($"property::{isStatic}::{type.FullName}::{name}", () => + PropertyInfo? property = this.GetCached<PropertyInfo>($"property::{isStatic}::{type.FullName}::{name}", () => { - PropertyInfo propertyInfo = null; - for (; type != null && propertyInfo == null; type = type.BaseType) - propertyInfo = type.GetProperty(name, bindingFlags); - return propertyInfo; + for (Type? curType = type; curType != null; curType = curType.BaseType) + { + PropertyInfo? propertyInfo = type.GetProperty(name, bindingFlags); + if (propertyInfo != null) + { + type = curType; + return propertyInfo; + } + } + + return null; }); return property != null @@ -177,18 +200,25 @@ namespace StardewModdingAPI.Framework.Reflection /// <summary>Get a method from the type hierarchy.</summary> /// <param name="type">The type which has the method.</param> - /// <param name="obj">The object which has the method.</param> + /// <param name="obj">The object which has the method, or <c>null</c> for a static method.</param> /// <param name="name">The method name.</param> /// <param name="bindingFlags">The reflection binding which flags which indicates what type of method to find.</param> - private IReflectedMethod GetMethodFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags) + private IReflectedMethod? GetMethodFromHierarchy(Type type, object? obj, string name, BindingFlags bindingFlags) { bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); - MethodInfo method = this.GetCached($"method::{isStatic}::{type.FullName}::{name}", () => + 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; + for (Type? curType = type; curType != null; curType = curType.BaseType) + { + MethodInfo? methodInfo = type.GetMethod(name, bindingFlags); + if (methodInfo != null) + { + type = curType; + return methodInfo; + } + } + + return null; }); return method != null @@ -200,7 +230,8 @@ namespace StardewModdingAPI.Framework.Reflection /// <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 + private TMemberInfo? GetCached<TMemberInfo>(string key, Func<TMemberInfo?> fetch) + where TMemberInfo : MemberInfo { // get from cache if (this.Cache.Contains(key)) @@ -212,8 +243,8 @@ namespace StardewModdingAPI.Framework.Reflection } // fetch & cache new value - TMemberInfo result = fetch(); - CacheEntry cacheEntry = new(result != null, result); + TMemberInfo? result = fetch(); + CacheEntry cacheEntry = new(result); this.Cache.Add(key, cacheEntry, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry }); return result; } |