Skip to content

Commit 99a71f5

Browse files
chore: reflection utility refactoring. proper exception throws added (#110)
1 parent 29e4dce commit 99a71f5

File tree

2 files changed

+69
-25
lines changed

2 files changed

+69
-25
lines changed

Runtime/Utilities/ReflectionUtility.cs

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,60 +11,74 @@ namespace StansAssets.Foundation
1111
/// </summary>
1212
public static class ReflectionUtility
1313
{
14-
static readonly string[] s_BuiltInAssemblyPrefixes = { "Mono.", "Unity.", "UnityEngine", "UnityEditor", "System", "mscorlib" };
14+
/// <summary>
15+
/// Collection of predefined built-in assembly prefixes. <c>Mono.</c>, <c>UnityEditor.</c>, <c>Unity.</c>, <c>UnityEngine</c>, <c>System</c> and <c>mscorlib</c> prefixes included.
16+
/// </summary>
17+
public static readonly string[] BuiltInAssemblyPrefixes = { "Mono.", "UnityEditor.", "Unity.", "UnityEngine", "System", "mscorlib" };
1518

1619
/// <summary>
1720
/// Creates an instance of the specified <see cref="System.Type"/> using that type's parameterless constructor.
1821
/// </summary>
1922
/// <param name="typeFullName">Full type name of the instance to create.</param>
2023
/// <returns>New <see cref="System.Object"/> instance of the specified type.</returns>
24+
/// <exception cref="ArgumentNullException"><paramref name="typeFullName"/> parameter is <c>null</c>.</exception>
25+
/// <exception cref="ArgumentException"><see cref="System.Type"/> specified with <paramref name="typeFullName"/> doesn't have default parameterless constructor.</exception>
2126
public static object CreateInstance(string typeFullName)
2227
{
28+
if (typeFullName == null)
29+
throw new ArgumentNullException(nameof(typeFullName));
30+
2331
var type = FindType(typeFullName);
24-
return type != null && type.HasDefaultConstructor()
25-
? Activator.CreateInstance(type)
26-
: null;
32+
if (!type.HasDefaultConstructor())
33+
throw new ArgumentException($"Type {typeFullName} doesn't have default parameterless constructor.");
34+
35+
return Activator.CreateInstance(type);
2736
}
2837

2938
/// <summary>
3039
/// Searches for the specified <see cref="System.Type"/> in all assemblies of the current application domain.
3140
/// </summary>
3241
/// <param name="typeFullName">Full type's name to search for.</param>
3342
/// <returns><see cref="System.Type"/> object found via specified <paramref name="typeFullName"/>.</returns>
43+
/// <exception cref="ArgumentNullException"><paramref name="typeFullName"/> parameter is <c>null</c>.</exception>
44+
/// <exception cref="InvalidOperationException">No types matching <paramref name="typeFullName"/> found in current application domain.</exception>
3445
public static Type FindType(string typeFullName)
3546
{
47+
if (typeFullName == null)
48+
throw new ArgumentNullException(nameof(typeFullName));
49+
3650
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
3751
return assemblies
3852
.SelectMany(assembly => assembly.GetTypes())
39-
.FirstOrDefault(type => type.FullName == null || type.FullName.Equals(typeFullName));
53+
.First(type => type.FullName == null || type.FullName.Equals(typeFullName));
4054
}
4155

4256
/// <summary>
4357
/// Searched for the implementations of the <see cref="System.Type"/> specified with <typeparamref name="T"/>.
4458
/// </summary>
45-
/// <param name="ignoreBuiltIn"><c>true</c> if the built-in assemblies have to be skipped. If set to <c>false</c>, no assemblies will be skipped.</param>
59+
/// <param name="ignoreAssemblyPrefixes">Collection of assembly prefixes to skip. The <see cref="System.Reflection.Assembly"/> will be ignored if its name <i><b>starts with</b></i> one of these prefixes.</param>
4660
/// <typeparam name="T">Specifies the <see cref="System.Type"/> whose implementations to search for.</typeparam>
4761
/// <returns>A collection of <see cref="System.Type"/> objects that are implementations of <typeparamref name="T"/>.</returns>
48-
public static IEnumerable<Type> FindImplementationsOf<T>(bool ignoreBuiltIn = false)
62+
public static IEnumerable<Type> FindImplementationsOf<T>(IEnumerable<string> ignoreAssemblyPrefixes = null)
4963
{
5064
var baseType = typeof(T);
51-
return FindImplementationsOf(baseType, ignoreBuiltIn);
65+
return FindImplementationsOf(baseType, ignoreAssemblyPrefixes);
5266
}
5367

5468
/// <summary>
5569
/// Gets the assemblies that have been loaded into the execution context of this application domain.
5670
/// </summary>
57-
/// <param name="ignoreBuiltIn"><c>true</c> if the built-in assemblies have to be skipped. If set to <c>false</c>, no assemblies will be skipped.</param>
71+
/// <param name="ignoreAssemblyPrefixes">Collection of assembly prefixes to skip. The <see cref="System.Reflection.Assembly"/> will be ignored if its name <i><b>starts with</b></i> one of these prefixes.</param>
5872
/// <returns>A collection of assemblies in this application domain.</returns>
59-
public static IEnumerable<Assembly> GetAssemblies(bool ignoreBuiltIn = false)
73+
public static IEnumerable<Assembly> GetAssemblies(IEnumerable<string> ignoreAssemblyPrefixes = null)
6074
{
6175
IEnumerable<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies();
6276

63-
if (ignoreBuiltIn)
77+
if (ignoreAssemblyPrefixes != null)
6478
{
6579
assemblies = assemblies.Where(assembly => {
6680
var assemblyName = assembly.GetName().Name;
67-
return !s_BuiltInAssemblyPrefixes.Any(prefix => assemblyName.StartsWith(prefix));
81+
return !BuiltInAssemblyPrefixes.Any(prefix => assemblyName.StartsWith(prefix));
6882
});
6983
}
7084

@@ -75,11 +89,15 @@ public static IEnumerable<Assembly> GetAssemblies(bool ignoreBuiltIn = false)
7589
/// Searched for the implementations of the <see cref="System.Type"/> specified with <paramref name="baseType"/>.
7690
/// </summary>
7791
/// <param name="baseType">Specifies the <see cref="System.Type"/> whose implementations to search for.</param>
78-
/// <param name="ignoreBuiltIn"><c>true</c> if the built-in assemblies have to be skipped. If set to <c>false</c>, no assemblies will be skipped.</param>
92+
/// <param name="ignoreAssemblyPrefixes">Collection of assembly prefixes to skip. The <see cref="System.Reflection.Assembly"/> will be ignored if its name <i><b>starts with</b></i> one of these prefixes.</param>
7993
/// <returns>A collection of <see cref="System.Type"/> objects that are implementations of <paramref name="baseType"/>.</returns>
80-
public static IEnumerable<Type> FindImplementationsOf(Type baseType, bool ignoreBuiltIn = false)
94+
/// <exception cref="ArgumentNullException"><paramref name="baseType"/> parameter is <c>null</c>.</exception>
95+
public static IEnumerable<Type> FindImplementationsOf(Type baseType, IEnumerable<string> ignoreAssemblyPrefixes = null)
8196
{
82-
var assemblies = GetAssemblies(ignoreBuiltIn);
97+
if (baseType == null)
98+
throw new ArgumentNullException(nameof(baseType));
99+
100+
var assemblies = GetAssemblies(ignoreAssemblyPrefixes);
83101

84102
return assemblies
85103
.SelectMany(assembly => assembly.GetTypes())
@@ -93,9 +111,22 @@ public static IEnumerable<Type> FindImplementationsOf(Type baseType, bool ignore
93111
/// <param name="propName">The string containing the name of the public property to get.</param>
94112
/// <param name="bindingAttr">A bitwise combination of the enumeration values that specify how the search is conducted.</param>
95113
/// <returns>The property value of the specified object.</returns>
114+
/// <exception cref="ArgumentNullException"><paramref name="propName"/> parameter is <c>null</c>.</exception>
115+
/// <exception cref="TargetException">The target <paramref name="src"/> object is <c>null</c>.</exception>
116+
/// <exception cref="ArgumentException">Property specified with the <paramref name="propName"/> not found.</exception>
96117
public static object GetPropertyValue(object src, string propName, BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public)
97118
{
98-
return src.GetType().GetProperty(propName, bindingAttr)?.GetValue(src, null);
119+
if (propName == null)
120+
throw new ArgumentNullException(nameof(propName));
121+
122+
if (src == null)
123+
throw new TargetException($"Target {nameof(src)} object is null.");
124+
125+
var property = src.GetType().GetProperty(propName, bindingAttr);
126+
if (property == null)
127+
throw new ArgumentException($"Property with '{propName}' name not found");
128+
129+
return property.GetValue(src, null);
99130
}
100131

101132
/// <summary>
@@ -106,22 +137,35 @@ public static object GetPropertyValue(object src, string propName, BindingFlags
106137
/// <param name="propValue">The new property value.</param>
107138
/// <param name="bindingAttr">A bitwise combination of the enumeration values that specify how the search is conducted.</param>
108139
/// <typeparam name="T">Specifies the <see cref="System.Type"/> of property value to set.</typeparam>
140+
/// <exception cref="ArgumentNullException"><paramref name="propName"/> parameter is <c>null</c>.</exception>
141+
/// <exception cref="TargetException">The target <paramref name="src"/> object is <c>null</c>.</exception>
142+
/// <exception cref="ArgumentException">Property specified with the <paramref name="propName"/> not found.</exception>
109143
public static void SetPropertyValue<T>(object src, string propName, T propValue, BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public)
110144
{
111-
src.GetType().GetProperty(propName, bindingAttr)?.SetValue(src, propValue);
145+
if (propName == null)
146+
throw new ArgumentNullException(nameof(propName));
147+
148+
if (src == null)
149+
throw new TargetException($"Target {nameof(src)} object is null.");
150+
151+
var property = src.GetType().GetProperty(propName, bindingAttr);
152+
if (property == null)
153+
throw new ArgumentException($"Property with '{propName}' name not found");
154+
155+
property.SetValue(src, propValue);
112156
}
113157

114158
/// <summary>
115159
/// Searches for the methods with custom attribute of type <typeparamref name="T"/>.
116160
/// </summary>
117161
/// <param name="methodBindingFlags">A bitwise combination of the enumeration values that specify how the search is conducted.</param>
118162
/// <param name="inherit"><c>true</c> to search this member's inheritance chain to find the attributes; otherwise, <c>false</c>.</param>
119-
/// <param name="ignoreBuiltIn"><c>true</c> if the built-in assemblies have to be skipped. If set to <c>false</c>, no assemblies will be skipped.</param>
163+
/// <param name="ignoreAssemblyPrefixes">Collection of assembly prefixes to skip. The <see cref="System.Reflection.Assembly"/> will be ignored if its name <i><b>starts with</b></i> one of these prefixes.</param>
120164
/// <typeparam name="T">Specifies the <see cref="System.Type"/> of the custom attribute to search for.</typeparam>
121165
/// <returns>A collection of <see cref="System.Reflection.MethodInfo"/> objects representing all methods defined for the current <see cref="System.Type"/> that match the specified binding constraints and attributes type.</returns>
122-
public static IEnumerable<MethodInfo> FindMethodsWithCustomAttributes<T>(BindingFlags methodBindingFlags = BindingFlags.Instance | BindingFlags.Public, bool inherit = true, bool ignoreBuiltIn = false) where T : Attribute
166+
public static IEnumerable<MethodInfo> FindMethodsWithCustomAttributes<T>(BindingFlags methodBindingFlags = BindingFlags.Instance | BindingFlags.Public, bool inherit = true, IEnumerable<string> ignoreAssemblyPrefixes = null) where T : Attribute
123167
{
124-
var assemblies = GetAssemblies(ignoreBuiltIn);
168+
var assemblies = GetAssemblies(ignoreAssemblyPrefixes);
125169

126170
return assemblies
127171
.SelectMany(assembly => assembly.GetTypes())

Tests/Editor/Utilities/ReflectionUtilityTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public void CreateInstanceValid(Type type)
9595
[TestCase(typeof(ClassWithPrivateConstructor))]
9696
public void CreateInstanceInvalid(Type type)
9797
{
98-
Assert.IsNull(ReflectionUtility.CreateInstance(type.FullName), $"Failed to create instance for:{type.FullName}");
98+
Assert.Throws<ArgumentException>(() => ReflectionUtility.CreateInstance(type.FullName), $"Failed to create instance for:{type.FullName}");
9999
}
100100

101101
[TestCase("System.Int32")]
@@ -108,7 +108,7 @@ public void FindTypeValid(string fullTypeName)
108108
[TestCase("System.ClassDoesNotExist")]
109109
public void FindTypeInvalid(string fullTypeName)
110110
{
111-
Assert.IsNull(ReflectionUtility.FindType(fullTypeName), $"Type {fullTypeName} not found");
111+
Assert.Throws<InvalidOperationException>(() => ReflectionUtility.FindType(fullTypeName), $"Type {fullTypeName} should NOT be found in this case");
112112
}
113113

114114
class TestAssembly
@@ -148,7 +148,7 @@ public void GetAllAssemblies()
148148
[Test]
149149
public void GetAllAssembliesWithoutBuiltIn()
150150
{
151-
var assemblies = ReflectionUtility.GetAssemblies(true).ToList();
151+
var assemblies = ReflectionUtility.GetAssemblies(ReflectionUtility.BuiltInAssemblyPrefixes).ToList();
152152
Assert.True(assemblies.Any(), "Assemblies collection is empty");
153153

154154
var assembliesSearchMap = new Dictionary<TestAssembly, bool>();
@@ -270,8 +270,8 @@ public void SetPropertyValue()
270270
[Test]
271271
public void FindMethodsWithCustomAttributes()
272272
{
273-
var allMethodInfos = ReflectionUtility.FindMethodsWithCustomAttributes<FancyAttribute>(ignoreBuiltIn: true).ToList();
274-
var nonInheritedMethodInfos = ReflectionUtility.FindMethodsWithCustomAttributes<FancyAttribute>(inherit: false, ignoreBuiltIn: true).ToList();
273+
var allMethodInfos = ReflectionUtility.FindMethodsWithCustomAttributes<FancyAttribute>(ignoreAssemblyPrefixes: ReflectionUtility.BuiltInAssemblyPrefixes).ToList();
274+
var nonInheritedMethodInfos = ReflectionUtility.FindMethodsWithCustomAttributes<FancyAttribute>(inherit: false, ignoreAssemblyPrefixes: ReflectionUtility.BuiltInAssemblyPrefixes).ToList();
275275

276276
Assert.True(allMethodInfos.Count == 2, "Incorrect amount of methods with custom attribute (inheritances included)");
277277
Assert.True(nonInheritedMethodInfos.Count == 1, "Incorrect amount of methods with custom attribute (inheritances NOT included)");

0 commit comments

Comments
 (0)