diff options
author | Jesse Plamondon-Willard <github@jplamondonw.com> | 2017-02-07 21:07:57 -0500 |
---|---|---|
committer | Jesse Plamondon-Willard <github@jplamondonw.com> | 2017-02-07 21:07:57 -0500 |
commit | 8efa5f32c1721a1ee60f3783022453c1dfb69d91 (patch) | |
tree | b01de092f8f3d741b802f9ea0b85c840b38e3b20 /src | |
parent | cec74697866da2d67a62c3a4ef10d605592e4152 (diff) | |
download | SMAPI-8efa5f32c1721a1ee60f3783022453c1dfb69d91.tar.gz SMAPI-8efa5f32c1721a1ee60f3783022453c1dfb69d91.tar.bz2 SMAPI-8efa5f32c1721a1ee60f3783022453c1dfb69d91.zip |
add reflectionHelper.GetPrivateProperty<T> (#231)
Diffstat (limited to 'src')
-rw-r--r-- | src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs | 93 | ||||
-rw-r--r-- | src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs | 57 | ||||
-rw-r--r-- | src/StardewModdingAPI/IPrivateProperty.cs | 26 | ||||
-rw-r--r-- | src/StardewModdingAPI/IReflectionHelper.cs | 14 | ||||
-rw-r--r-- | src/StardewModdingAPI/StardewModdingAPI.csproj | 2 |
5 files changed, 192 insertions, 0 deletions
diff --git a/src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs b/src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs new file mode 100644 index 00000000..08204b7e --- /dev/null +++ b/src/StardewModdingAPI/Framework/Reflection/PrivateProperty.cs @@ -0,0 +1,93 @@ +using System; +using System.Reflection; + +namespace StardewModdingAPI.Framework.Reflection +{ + /// <summary>A private property obtained through reflection.</summary> + /// <typeparam name="TValue">The property value type.</typeparam> + internal class PrivateProperty<TValue> : IPrivateProperty<TValue> + { + /********* + ** Properties + *********/ + /// <summary>The type that has the field.</summary> + private readonly Type ParentType; + + /// <summary>The object that has the instance field (if applicable).</summary> + private readonly object Parent; + + /// <summary>The display name shown in error messages.</summary> + private string DisplayName => $"{this.ParentType.FullName}::{this.PropertyInfo.Name}"; + + + /********* + ** Accessors + *********/ + /// <summary>The reflection metadata.</summary> + public PropertyInfo PropertyInfo { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="parentType">The type that has the field.</param> + /// <param name="obj">The object that has the instance field (if applicable).</param> + /// <param name="property">The reflection metadata.</param> + /// <param name="isStatic">Whether the field is static.</param> + /// <exception cref="ArgumentNullException">The <paramref name="parentType"/> or <paramref name="property"/> is null.</exception> + /// <exception cref="ArgumentException">The <paramref name="obj"/> is null for a non-static field, or not null for a static field.</exception> + public PrivateProperty(Type parentType, object obj, PropertyInfo property, bool isStatic) + { + // validate + if (parentType == null) + throw new ArgumentNullException(nameof(parentType)); + if (property == null) + throw new ArgumentNullException(nameof(property)); + if (isStatic && obj != null) + throw new ArgumentException("A static property cannot have an object instance."); + if (!isStatic && obj == null) + throw new ArgumentException("A non-static property must have an object instance."); + + // save + this.ParentType = parentType; + this.Parent = obj; + this.PropertyInfo = property; + } + + /// <summary>Get the property value.</summary> + public TValue GetValue() + { + try + { + return (TValue)this.PropertyInfo.GetValue(this.Parent); + } + catch (InvalidCastException) + { + throw new InvalidCastException($"Can't convert the private {this.DisplayName} property from {this.PropertyInfo.PropertyType.FullName} to {typeof(TValue).FullName}."); + } + catch (Exception ex) + { + throw new Exception($"Couldn't get the value of the private {this.DisplayName} property", ex); + } + } + + /// <summary>Set the property value.</summary> + //// <param name="value">The value to set.</param> + public void SetValue(TValue value) + { + try + { + this.PropertyInfo.SetValue(this.Parent, value); + } + catch (InvalidCastException) + { + throw new InvalidCastException($"Can't assign the private {this.DisplayName} property a {typeof(TValue).FullName} value, must be compatible with {this.PropertyInfo.PropertyType.FullName}."); + } + catch (Exception ex) + { + throw new Exception($"Couldn't set the value of the private {this.DisplayName} property", ex); + } + } + } +} diff --git a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs index edf59b81..7a5789dc 100644 --- a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs +++ b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs @@ -59,6 +59,41 @@ namespace StardewModdingAPI.Framework.Reflection } /**** + ** Properties + ****/ + /// <summary>Get a private instance property.</summary> + /// <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 private property is not found.</param> + public IPrivateProperty<TValue> GetPrivateProperty<TValue>(object obj, string name, bool required = true) + { + // validate + if (obj == null) + throw new ArgumentNullException(nameof(obj), "Can't get a private instance property from a null object."); + + // get property from hierarchy + IPrivateProperty<TValue> property = this.GetPropertyFromHierarchy<TValue>(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic); + if (required && property == null) + throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a private '{name}' instance property."); + return property; + } + + /// <summary>Get a private 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 private property is not found.</param> + public IPrivateProperty<TValue> GetPrivateProperty<TValue>(Type type, string name, bool required = true) + { + // get field from hierarchy + IPrivateProperty<TValue> property = this.GetPropertyFromHierarchy<TValue>(type, null, name, BindingFlags.NonPublic | BindingFlags.Static); + if (required && property == null) + throw new InvalidOperationException($"The {type.FullName} object doesn't have a private '{name}' static property."); + return property; + } + + /**** ** Field values ** (shorthand since this is the most common case) ****/ @@ -192,6 +227,28 @@ namespace StardewModdingAPI.Framework.Reflection : null; } + /// <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="name">The property name.</param> + /// <param name="bindingFlags">The reflection binding which flags which indicates what type of property to find.</param> + private IPrivateProperty<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 propertyInfo = null; + for (; type != null && propertyInfo == null; type = type.BaseType) + propertyInfo = type.GetProperty(name, bindingFlags); + return propertyInfo; + }); + + return property != null + ? new PrivateProperty<TValue>(type, obj, property, isStatic) + : null; + } + /// <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> diff --git a/src/StardewModdingAPI/IPrivateProperty.cs b/src/StardewModdingAPI/IPrivateProperty.cs new file mode 100644 index 00000000..8d67fa7a --- /dev/null +++ b/src/StardewModdingAPI/IPrivateProperty.cs @@ -0,0 +1,26 @@ +using System.Reflection; + +namespace StardewModdingAPI +{ + /// <summary>A private property obtained through reflection.</summary> + /// <typeparam name="TValue">The property value type.</typeparam> + public interface IPrivateProperty<TValue> + { + /********* + ** Accessors + *********/ + /// <summary>The reflection metadata.</summary> + PropertyInfo PropertyInfo { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Get the property value.</summary> + TValue GetValue(); + + /// <summary>Set the property value.</summary> + //// <param name="value">The value to set.</param> + void SetValue(TValue value); + } +}
\ No newline at end of file diff --git a/src/StardewModdingAPI/IReflectionHelper.cs b/src/StardewModdingAPI/IReflectionHelper.cs index 5d747eda..77943c6c 100644 --- a/src/StardewModdingAPI/IReflectionHelper.cs +++ b/src/StardewModdingAPI/IReflectionHelper.cs @@ -22,6 +22,20 @@ namespace StardewModdingAPI /// <param name="required">Whether to throw an exception if the private field is not found.</param> IPrivateField<TValue> GetPrivateField<TValue>(Type type, string name, bool required = true); + /// <summary>Get a private instance property.</summary> + /// <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 private property is not found.</param> + IPrivateProperty<TValue> GetPrivateProperty<TValue>(object obj, string name, bool required = true); + + /// <summary>Get a private 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 private property is not found.</param> + IPrivateProperty<TValue> GetPrivateProperty<TValue>(Type type, string name, bool required = true); + /// <summary>Get the value of a private instance field.</summary> /// <typeparam name="TValue">The field type.</typeparam> /// <param name="obj">The object which has the field.</param> diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index aa8cad34..cecfc831 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -149,6 +149,7 @@ <Compile Include="Events\GraphicsEvents.cs" /> <Compile Include="Framework\AssemblyDefinitionResolver.cs" /> <Compile Include="Framework\AssemblyParseResult.cs" /> + <Compile Include="Framework\Reflection\PrivateProperty.cs" /> <Compile Include="Framework\Serialisation\SemanticVersionConverter.cs" /> <Compile Include="IModRegistry.cs" /> <Compile Include="Events\LocationEvents.cs" /> @@ -170,6 +171,7 @@ <Compile Include="IMod.cs" /> <Compile Include="IModHelper.cs" /> <Compile Include="Framework\LogFileManager.cs" /> + <Compile Include="IPrivateProperty.cs" /> <Compile Include="ISemanticVersion.cs" /> <Compile Include="LogLevel.cs" /> <Compile Include="Framework\ModRegistry.cs" /> |