using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; using Swan.Configuration; using Swan.Reflection; namespace 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) : default; } /// /// 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 [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. /// /// type public static bool TryParseBasicType(this Type type, object value, out object? result) { if (type == null) throw new ArgumentNullException(nameof(type)); if (type != typeof(bool)) return TryParseBasicType(type, value.ToStringInvariant(), out result); result = value.ToBoolean(); return true; } /// /// Tries to parse using the basic types. /// /// The type. /// The value. /// The result. /// /// true if parsing was successful; otherwise, false. /// /// type public static bool TryParseBasicType(this Type type, string value, out object? result) { if (type == null) throw new ArgumentNullException(nameof(type)); result = null; return Definitions.BasicTypesInfo.Value.ContainsKey(type) && Definitions.BasicTypesInfo.Value[type].TryParse(value, out result); } /// /// Tries the type of the set basic value to a property. /// /// The property information. /// The value. /// The object. /// /// true if parsing was successful; otherwise, false. /// /// propertyInfo. public static bool TrySetBasicType(this PropertyInfo propertyInfo, object value, object target) { if (propertyInfo == null) throw new ArgumentNullException(nameof(propertyInfo)); try { if (propertyInfo.PropertyType.TryParseBasicType(value, out var propertyValue)) { propertyInfo.SetValue(target, 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. /// /// type public static bool TrySetArrayBasicType(this Type type, object value, Array target, int index) { if (type == null) throw new ArgumentNullException(nameof(type)); if (target == null) return false; try { if (value == null) { target.SetValue(null, index); return true; } if (type.TryParseBasicType(value, out var propertyValue)) { target.SetValue(propertyValue, index); return true; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { target.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. /// /// propertyInfo. public static bool TrySetArray(this PropertyInfo propertyInfo, IEnumerable? value, object obj) { if (propertyInfo == null) throw new ArgumentNullException(nameof(propertyInfo)); var elementType = propertyInfo.PropertyType.GetElementType(); if (elementType == null || value == 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. /// propertyInfo. public static string? ToFormattedString(this PropertyInfo propertyInfo, object target) { if (propertyInfo == null) throw new ArgumentNullException(nameof(propertyInfo)); try { var value = propertyInfo.GetValue(target); var attr = AttributeCache.DefaultCache.Value.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); // TODO: Fix public logic return !nonPublic && !CacheGetMethods.Value.ContainsKey(key) && !propertyInfo.GetGetMethod(true).IsPublic ? null : CacheGetMethods.Value .GetOrAdd(key, x => y => x.Item2.GetGetMethod(nonPublic).Invoke(y, null)); //y => x => y.Item2.CreatePropertyProxy().GetValue(x)); } /// /// 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)); //y => (obj, args) => y.Item2.CreatePropertyProxy().SetValue(obj, args)); } /// /// Convert a string to a boolean. /// /// The string. /// /// true if the string represents a valid truly value, otherwise false. /// public static bool ToBoolean(this string str) { try { return Convert.ToBoolean(str); } catch (FormatException) { // ignored } try { return Convert.ToBoolean(Convert.ToInt32(str)); } catch { // ignored } return false; } /// /// Creates a property proxy that stores getter and setter delegates. /// /// The property information. /// /// The property proxy. /// /// this. public static IPropertyProxy? CreatePropertyProxy(this PropertyInfo @this) { if (@this == null) throw new ArgumentNullException(nameof(@this)); var genericType = typeof(PropertyProxy<,>) .MakeGenericType(@this.DeclaringType, @this.PropertyType); return Activator.CreateInstance(genericType, @this) as IPropertyProxy; } /// /// Convert a object to a boolean. /// /// The value. /// /// true if the string represents a valid truly value, otherwise false. /// public static bool ToBoolean(this object value) => value.ToStringInvariant().ToBoolean(); private static string ConvertObjectAndFormat(Type propertyType, object value, string format) { if (propertyType == typeof(DateTime) || propertyType == typeof(DateTime?)) return Convert.ToDateTime(value, CultureInfo.InvariantCulture).ToString(format); if (propertyType == typeof(int) || propertyType == typeof(int?)) return Convert.ToInt32(value, CultureInfo.InvariantCulture).ToString(format); if (propertyType == typeof(decimal) || propertyType == typeof(decimal?)) return Convert.ToDecimal(value, CultureInfo.InvariantCulture).ToString(format); if (propertyType == typeof(double) || propertyType == typeof(double?)) return Convert.ToDouble(value, CultureInfo.InvariantCulture).ToString(format); if (propertyType == typeof(byte) || propertyType == typeof(byte?)) return Convert.ToByte(value, CultureInfo.InvariantCulture).ToString(format); return value?.ToString() ?? string.Empty; } } }