RaspberryIO/Unosquare.Swan.Lite/Extensions.Reflection.cs
2019-12-04 17:10:06 +01:00

447 lines
17 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using Unosquare.Swan.Attributes;
namespace Unosquare.Swan {
/// <summary>
/// Provides various extension methods for Reflection and Types.
/// </summary>
public static class ReflectionExtensions {
private static readonly Lazy<ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Func<Object, Object>>> CacheGetMethods =
new Lazy<ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Func<Object, Object>>>(() => new ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Func<Object, Object>>(), true);
private static readonly Lazy<ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Action<Object, Object[]>>> CacheSetMethods =
new Lazy<ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Action<Object, Object[]>>>(() => new ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Action<Object, Object[]>>(), true);
#region Assembly Extensions
/// <summary>
/// Gets all types within an assembly in a safe manner.
/// </summary>
/// <param name="assembly">The assembly.</param>
/// <returns>
/// Array of Type objects representing the types specified by an assembly.
/// </returns>
/// <exception cref="ArgumentNullException">assembly.</exception>
public static IEnumerable<Type> GetAllTypes(this Assembly assembly) {
if(assembly == null) {
throw new ArgumentNullException(nameof(assembly));
}
try {
return assembly.GetTypes();
} catch(ReflectionTypeLoadException e) {
return e.Types.Where(t => t != null);
}
}
#endregion
#region Type Extensions
/// <summary>
/// The closest programmatic equivalent of default(T).
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// Default value of this type.
/// </returns>
/// <exception cref="ArgumentNullException">type.</exception>
public static Object GetDefault(this Type type) {
if(type == null) {
throw new ArgumentNullException(nameof(type));
}
return type.IsValueType() ? Activator.CreateInstance(type) : null;
}
/// <summary>
/// Determines whether this type is compatible with ICollection.
/// </summary>
/// <param name="sourceType">The type.</param>
/// <returns>
/// <c>true</c> if the specified source type is collection; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">sourceType.</exception>
public static Boolean IsCollection(this Type sourceType) {
if(sourceType == null) {
throw new ArgumentNullException(nameof(sourceType));
}
return sourceType != typeof(String) &&
typeof(IEnumerable).IsAssignableFrom(sourceType);
}
/// <summary>
/// Gets a method from a type given the method name, binding flags, generic types and parameter types.
/// </summary>
/// <param name="type">Type of the source.</param>
/// <param name="bindingFlags">The binding flags.</param>
/// <param name="methodName">Name of the method.</param>
/// <param name="genericTypes">The generic types.</param>
/// <param name="parameterTypes">The parameter types.</param>
/// <returns>
/// An object that represents the method with the specified name.
/// </returns>
/// <exception cref="System.Reflection.AmbiguousMatchException">
/// The exception that is thrown when binding to a member results in more than one member matching the
/// binding criteria. This class cannot be inherited.
/// </exception>
public static MethodInfo GetMethod(
this Type type,
BindingFlags bindingFlags,
String methodName,
Type[] genericTypes,
Type[] parameterTypes) {
if(type == null) {
throw new ArgumentNullException(nameof(type));
}
if(methodName == null) {
throw new ArgumentNullException(nameof(methodName));
}
if(genericTypes == null) {
throw new ArgumentNullException(nameof(genericTypes));
}
if(parameterTypes == null) {
throw new ArgumentNullException(nameof(parameterTypes));
}
List<MethodInfo> methods = type
.GetMethods(bindingFlags)
.Where(mi => String.Equals(methodName, mi.Name, StringComparison.Ordinal))
.Where(mi => mi.ContainsGenericParameters)
.Where(mi => mi.GetGenericArguments().Length == genericTypes.Length)
.Where(mi => mi.GetParameters().Length == parameterTypes.Length)
.Select(mi => mi.MakeGenericMethod(genericTypes))
.Where(mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes))
.ToList();
return methods.Count > 1 ? throw new AmbiguousMatchException() : methods.FirstOrDefault();
}
/// <summary>
/// Determines whether this instance is class.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if the specified type is class; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsClass(this Type type) => type.GetTypeInfo().IsClass;
/// <summary>
/// Determines whether this instance is abstract.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if the specified type is abstract; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsAbstract(this Type type) => type.GetTypeInfo().IsAbstract;
/// <summary>
/// Determines whether this instance is interface.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if the specified type is interface; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsInterface(this Type type) => type.GetTypeInfo().IsInterface;
/// <summary>
/// Determines whether this instance is primitive.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if the specified type is primitive; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsPrimitive(this Type type) => type.GetTypeInfo().IsPrimitive;
/// <summary>
/// Determines whether [is value type].
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if [is value type] [the specified type]; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsValueType(this Type type) => type.GetTypeInfo().IsValueType;
/// <summary>
/// Determines whether [is generic type].
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if [is generic type] [the specified type]; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsGenericType(this Type type) => type.GetTypeInfo().IsGenericType;
/// <summary>
/// Determines whether [is generic parameter].
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if [is generic parameter] [the specified type]; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsGenericParameter(this Type type) => type.IsGenericParameter;
/// <summary>
/// Determines whether the specified attribute type is defined.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="attributeType">Type of the attribute.</param>
/// <param name="inherit">if set to <c>true</c> [inherit].</param>
/// <returns>
/// <c>true</c> if the specified attribute type is defined; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsDefined(this Type type, Type attributeType, Boolean inherit) =>
type.GetTypeInfo().IsDefined(attributeType, inherit);
/// <summary>
/// Gets the custom attributes.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="attributeType">Type of the attribute.</param>
/// <param name="inherit">if set to <c>true</c> [inherit].</param>
/// <returns>
/// Attributes associated with the property represented by this PropertyInfo object.
/// </returns>
public static Attribute[] GetCustomAttributes(this Type type, Type attributeType, Boolean inherit) =>
type.GetTypeInfo().GetCustomAttributes(attributeType, inherit).Cast<Attribute>().ToArray();
/// <summary>
/// Determines whether [is generic type definition].
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if [is generic type definition] [the specified type]; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsGenericTypeDefinition(this Type type) => type.GetTypeInfo().IsGenericTypeDefinition;
/// <summary>
/// Bases the type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>returns a type of data.</returns>
public static Type BaseType(this Type type) => type.GetTypeInfo().BaseType;
/// <summary>
/// Assemblies the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>returns an Assembly object.</returns>
public static Assembly Assembly(this Type type) => type.GetTypeInfo().Assembly;
/// <summary>
/// Determines whether [is i enumerable request].
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if [is i enumerable request] [the specified type]; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">type.</exception>
public static Boolean IsIEnumerable(this Type type)
=> type == null
? throw new ArgumentNullException(nameof(type))
: type.IsGenericType() && type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
#endregion
/// <summary>
/// Tries to parse using the basic types.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="value">The value.</param>
/// <param name="result">The result.</param>
/// <returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns>
public static Boolean TryParseBasicType(this Type type, Object value, out Object result)
=> TryParseBasicType(type, value.ToStringInvariant(), out result);
/// <summary>
/// Tries to parse using the basic types.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="value">The value.</param>
/// <param name="result">The result.</param>
/// <returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns>
public static Boolean TryParseBasicType(this Type type, String value, out Object result) {
result = null;
return Definitions.BasicTypesInfo.ContainsKey(type) && Definitions.BasicTypesInfo[type].TryParse(value, out result);
}
/// <summary>
/// Tries the type of the set basic value to a property.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <param name="obj">The object.</param>
/// <returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns>
public static Boolean TrySetBasicType(this PropertyInfo property, Object value, Object obj) {
try {
if(property.PropertyType.TryParseBasicType(value, out Object propertyValue)) {
property.SetValue(obj, propertyValue);
return true;
}
} catch {
// swallow
}
return false;
}
/// <summary>
/// Tries the type of the set to an array a basic type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="value">The value.</param>
/// <param name="array">The array.</param>
/// <param name="index">The index.</param>
/// <returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns>
public static Boolean TrySetArrayBasicType(this Type type, Object value, Array array, Int32 index) {
try {
if(value == null) {
array.SetValue(null, index);
return true;
}
if(type.TryParseBasicType(value, out Object propertyValue)) {
array.SetValue(propertyValue, index);
return true;
}
if(type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) {
array.SetValue(null, index);
return true;
}
} catch {
// swallow
}
return false;
}
/// <summary>
/// Tries to set a property array with another array.
/// </summary>
/// <param name="propertyInfo">The property.</param>
/// <param name="value">The value.</param>
/// <param name="obj">The object.</param>
/// <returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns>
public static Boolean TrySetArray(this PropertyInfo propertyInfo, IEnumerable<Object> value, Object obj) {
Type elementType = propertyInfo.PropertyType.GetElementType();
if(elementType == null) {
return false;
}
Array targetArray = Array.CreateInstance(elementType, value.Count());
Int32 i = 0;
foreach(Object sourceElement in value) {
Boolean result = elementType.TrySetArrayBasicType(sourceElement, targetArray, i++);
if(!result) {
return false;
}
}
propertyInfo.SetValue(obj, targetArray);
return true;
}
/// <summary>
/// Gets property actual value or <c>PropertyDisplayAttribute.DefaultValue</c> if presented.
///
/// If the <c>PropertyDisplayAttribute.Format</c> value is presented, the property value
/// will be formatted accordingly.
///
/// If the object contains a null value, a empty string will be returned.
/// </summary>
/// <param name="propertyInfo">The property information.</param>
/// <param name="obj">The object.</param>
/// <returns>The property value or null.</returns>
public static String ToFormattedString(this PropertyInfo propertyInfo, Object obj) {
try {
Object value = propertyInfo.GetValue(obj);
PropertyDisplayAttribute attr = Runtime.AttributeCache.RetrieveOne<PropertyDisplayAttribute>(propertyInfo);
if(attr == null) {
return value?.ToString() ?? String.Empty;
}
Object valueToFormat = value ?? attr.DefaultValue;
return String.IsNullOrEmpty(attr.Format)
? (valueToFormat?.ToString() ?? String.Empty)
: ConvertObjectAndFormat(propertyInfo.PropertyType, valueToFormat, attr.Format);
} catch {
return null;
}
}
/// <summary>
/// Gets a MethodInfo from a Property Get method.
/// </summary>
/// <param name="propertyInfo">The property information.</param>
/// <param name="nonPublic">if set to <c>true</c> [non public].</param>
/// <returns>
/// The cached MethodInfo.
/// </returns>
public static Func<Object, Object> GetCacheGetMethod(this PropertyInfo propertyInfo, Boolean nonPublic = false) {
Tuple<Boolean, PropertyInfo> key = Tuple.Create(!nonPublic, propertyInfo);
return !nonPublic && !CacheGetMethods.Value.ContainsKey(key) && !propertyInfo.GetGetMethod(true).IsPublic
? null
: CacheGetMethods.Value
.GetOrAdd(key,
x => y => x.Item2.GetGetMethod(nonPublic).Invoke(y, null));
}
/// <summary>
/// Gets a MethodInfo from a Property Set method.
/// </summary>
/// <param name="propertyInfo">The property information.</param>
/// <param name="nonPublic">if set to <c>true</c> [non public].</param>
/// <returns>
/// The cached MethodInfo.
/// </returns>
public static Action<Object, Object[]> GetCacheSetMethod(this PropertyInfo propertyInfo, Boolean nonPublic = false) {
Tuple<Boolean, PropertyInfo> key = Tuple.Create(!nonPublic, propertyInfo);
return !nonPublic && !CacheSetMethods.Value.ContainsKey(key) && !propertyInfo.GetSetMethod(true).IsPublic
? null
: CacheSetMethods.Value
.GetOrAdd(key,
x => (obj, args) => x.Item2.GetSetMethod(nonPublic).Invoke(obj, args));
}
private static String ConvertObjectAndFormat(Type propertyType, Object value, String format) => propertyType == typeof(DateTime) || propertyType == typeof(DateTime?)
? Convert.ToDateTime(value).ToString(format)
: propertyType == typeof(Int32) || propertyType == typeof(Int32?)
? Convert.ToInt32(value).ToString(format)
: propertyType == typeof(Decimal) || propertyType == typeof(Decimal?)
? Convert.ToDecimal(value).ToString(format)
: propertyType == typeof(Double) || propertyType == typeof(Double?)
? Convert.ToDouble(value).ToString(format)
: propertyType == typeof(Byte) || propertyType == typeof(Byte?)
? Convert.ToByte(value).ToString(format)
: value?.ToString() ?? String.Empty;
}
}