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 { /// /// 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 Boolean 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)); } List 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 Boolean 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 Boolean 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 Boolean 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 Boolean 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 Boolean 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 Boolean 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 Boolean 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 Boolean IsDefined(this Type type, Type attributeType, Boolean 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, Boolean 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 Boolean 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 Boolean 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 Boolean 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 Boolean 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 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; } /// /// 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 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; } /// /// Tries to set a property array with another array. /// /// The property. /// The value. /// The object. /// /// true if parsing was successful; otherwise, false. /// public static Boolean TrySetArray(this PropertyInfo propertyInfo, IEnumerable 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; } /// /// 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 { Object value = propertyInfo.GetValue(obj); PropertyDisplayAttribute attr = Runtime.AttributeCache.RetrieveOne(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; } } /// /// 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, Boolean nonPublic = false) { Tuple 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, Boolean nonPublic = false) { Tuple 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; } }