namespace Unosquare.Swan { using System; using System.Collections.Concurrent; using System.Collections; using System.Linq; using System.Reflection; using System.Collections.Generic; using Attributes; /// /// Provides various extension methods for Reflection and Types. /// public static class ReflectionExtensions { private static readonly Lazy, Func>> CacheGetMethods = new Lazy, Func>>(() => new ConcurrentDictionary, Func>(), true); private static readonly Lazy, Action>> CacheSetMethods = new Lazy, Action>>(() => new ConcurrentDictionary, Action>(), true); #region Assembly Extensions /// /// Gets all types within an assembly in a safe manner. /// /// The assembly. /// /// Array of Type objects representing the types specified by an assembly. /// /// assembly. public static IEnumerable 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 /// /// The closest programmatic equivalent of default(T). /// /// The type. /// /// Default value of this type. /// /// type. public static object GetDefault(this Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return type.IsValueType() ? Activator.CreateInstance(type) : null; } /// /// Determines whether this type is compatible with ICollection. /// /// The type. /// /// true if the specified source type is collection; otherwise, false. /// /// sourceType. public static bool IsCollection(this Type sourceType) { if (sourceType == null) throw new ArgumentNullException(nameof(sourceType)); return sourceType != typeof(string) && typeof(IEnumerable).IsAssignableFrom(sourceType); } /// /// Gets a method from a type given the method name, binding flags, generic types and parameter types. /// /// Type of the source. /// The binding flags. /// Name of the method. /// The generic types. /// The parameter types. /// /// An object that represents the method with the specified name. /// /// /// 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. /// 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)); var 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(); } /// /// Determines whether this instance is class. /// /// The type. /// /// true if the specified type is class; otherwise, false. /// public static bool IsClass(this Type type) => type.GetTypeInfo().IsClass; /// /// Determines whether this instance is abstract. /// /// The type. /// /// true if the specified type is abstract; otherwise, false. /// public static bool IsAbstract(this Type type) => type.GetTypeInfo().IsAbstract; /// /// Determines whether this instance is interface. /// /// The type. /// /// true if the specified type is interface; otherwise, false. /// public static bool IsInterface(this Type type) => type.GetTypeInfo().IsInterface; /// /// Determines whether this instance is primitive. /// /// The type. /// /// true if the specified type is primitive; otherwise, false. /// public static bool IsPrimitive(this Type type) => type.GetTypeInfo().IsPrimitive; /// /// Determines whether [is value type]. /// /// The type. /// /// true if [is value type] [the specified type]; otherwise, false. /// public static bool IsValueType(this Type type) => type.GetTypeInfo().IsValueType; /// /// Determines whether [is generic type]. /// /// The type. /// /// true if [is generic type] [the specified type]; otherwise, false. /// public static bool IsGenericType(this Type type) => type.GetTypeInfo().IsGenericType; /// /// Determines whether [is generic parameter]. /// /// The type. /// /// true if [is generic parameter] [the specified type]; otherwise, false. /// public static bool IsGenericParameter(this Type type) => type.IsGenericParameter; /// /// Determines whether the specified attribute type is defined. /// /// The type. /// Type of the attribute. /// if set to true [inherit]. /// /// true if the specified attribute type is defined; otherwise, false. /// public static bool IsDefined(this Type type, Type attributeType, bool inherit) => type.GetTypeInfo().IsDefined(attributeType, inherit); /// /// Gets the custom attributes. /// /// The type. /// Type of the attribute. /// if set to true [inherit]. /// /// Attributes associated with the property represented by this PropertyInfo object. /// public static Attribute[] GetCustomAttributes(this Type type, Type attributeType, bool inherit) => type.GetTypeInfo().GetCustomAttributes(attributeType, inherit).Cast().ToArray(); /// /// Determines whether [is generic type definition]. /// /// The type. /// /// true if [is generic type definition] [the specified type]; otherwise, false. /// public static bool IsGenericTypeDefinition(this Type type) => type.GetTypeInfo().IsGenericTypeDefinition; /// /// Bases the type. /// /// The type. /// returns a type of data. public static Type BaseType(this Type type) => type.GetTypeInfo().BaseType; /// /// Assemblies the specified type. /// /// The type. /// returns an Assembly object. public static Assembly Assembly(this Type type) => type.GetTypeInfo().Assembly; /// /// Determines whether [is i enumerable request]. /// /// The type. /// /// true if [is i enumerable request] [the specified type]; otherwise, false. /// /// type. public static bool IsIEnumerable(this Type type) => type == null ? throw new ArgumentNullException(nameof(type)) : type.IsGenericType() && type.GetGenericTypeDefinition() == typeof(IEnumerable<>); #endregion /// /// Tries to parse using the basic types. /// /// The type. /// The value. /// The result. /// /// true if parsing was successful; otherwise, false. /// public static bool TryParseBasicType(this Type type, object value, out object result) => TryParseBasicType(type, value.ToStringInvariant(), out result); /// /// Tries to parse using the basic types. /// /// The type. /// The value. /// The result. /// /// true if parsing was successful; otherwise, false. /// public static bool TryParseBasicType(this Type type, string value, out object result) { result = null; return Definitions.BasicTypesInfo.ContainsKey(type) && Definitions.BasicTypesInfo[type].TryParse(value, out result); } /// /// Tries the type of the set basic value to a property. /// /// The property. /// The value. /// The object. /// /// true if parsing was successful; otherwise, false. /// public static bool TrySetBasicType(this PropertyInfo property, object value, object obj) { try { if (property.PropertyType.TryParseBasicType(value, out var propertyValue)) { property.SetValue(obj, propertyValue); return true; } } catch { // swallow } return false; } /// /// Tries the type of the set to an array a basic type. /// /// The type. /// The value. /// The array. /// The index. /// /// true if parsing was successful; otherwise, false. /// public static bool TrySetArrayBasicType(this Type type, object value, Array array, int index) { try { if (value == null) { array.SetValue(null, index); return true; } if (type.TryParseBasicType(value, out var 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; } /// /// Tries to set a property array with another array. /// /// The property. /// The value. /// The object. /// /// true if parsing was successful; otherwise, false. /// public static bool TrySetArray(this PropertyInfo propertyInfo, IEnumerable value, object obj) { var elementType = propertyInfo.PropertyType.GetElementType(); if (elementType == null) return false; var targetArray = Array.CreateInstance(elementType, value.Count()); var i = 0; foreach (var sourceElement in value) { var result = elementType.TrySetArrayBasicType(sourceElement, targetArray, i++); if (!result) return false; } propertyInfo.SetValue(obj, targetArray); return true; } /// /// Gets property actual value or PropertyDisplayAttribute.DefaultValue if presented. /// /// If the PropertyDisplayAttribute.Format value is presented, the property value /// will be formatted accordingly. /// /// If the object contains a null value, a empty string will be returned. /// /// The property information. /// The object. /// The property value or null. public static string ToFormattedString(this PropertyInfo propertyInfo, object obj) { try { var value = propertyInfo.GetValue(obj); var attr = Runtime.AttributeCache.RetrieveOne(propertyInfo); if (attr == null) return value?.ToString() ?? string.Empty; var valueToFormat = value ?? attr.DefaultValue; return string.IsNullOrEmpty(attr.Format) ? (valueToFormat?.ToString() ?? string.Empty) : ConvertObjectAndFormat(propertyInfo.PropertyType, valueToFormat, attr.Format); } catch { return null; } } /// /// Gets a MethodInfo from a Property Get method. /// /// The property information. /// if set to true [non public]. /// /// The cached MethodInfo. /// public static Func GetCacheGetMethod(this PropertyInfo propertyInfo, bool nonPublic = false) { var 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)); } /// /// Gets a MethodInfo from a Property Set method. /// /// The property information. /// if set to true [non public]. /// /// The cached MethodInfo. /// public static Action GetCacheSetMethod(this PropertyInfo propertyInfo, bool nonPublic = false) { var 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) { if (propertyType == typeof(DateTime) || propertyType == typeof(DateTime?)) return Convert.ToDateTime(value).ToString(format); if (propertyType == typeof(int) || propertyType == typeof(int?)) return Convert.ToInt32(value).ToString(format); if (propertyType == typeof(decimal) || propertyType == typeof(decimal?)) return Convert.ToDecimal(value).ToString(format); if (propertyType == typeof(double) || propertyType == typeof(double?)) return Convert.ToDouble(value).ToString(format); if (propertyType == typeof(byte) || propertyType == typeof(byte?)) return Convert.ToByte(value).ToString(format); return value?.ToString() ?? string.Empty; } } }