using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Serialization; using JD.Kernel.Classes; using JD.Kernel.StaticHelpers; using JetBrains.Annotations; namespace JD.Reflection { /// /// Helper class. /// public static class ReflectionHelper { #region Variables private static readonly List m_integerTypes = new List(); private static readonly List m_floatingPointTypes = new List(); private static readonly TypeConverterCache TypeConverter = new TypeConverterCache(); #endregion #region Constructor /// /// Static constructor. /// static ReflectionHelper() { // Integer types. m_integerTypes.Add(typeof(byte)); m_integerTypes.Add(typeof(sbyte)); m_integerTypes.Add(typeof(short)); m_integerTypes.Add(typeof(ushort)); m_integerTypes.Add(typeof(int)); m_integerTypes.Add(typeof(uint)); m_integerTypes.Add(typeof(long)); m_integerTypes.Add(typeof(ulong)); // Floating-point types. m_floatingPointTypes.Add(typeof(float)); m_floatingPointTypes.Add(typeof(double)); m_floatingPointTypes.Add(typeof(decimal)); } #endregion #region Methods #region Type Helpers /// /// Set a value of a member via reflection /// /// /// /// public static void SetValue(object instance, string memberName, object value) { foreach (ValueMemberInfo valueMemberInfo in ValueMemberInfo.GetValueMembers(instance.GetType(), true)) { if (valueMemberInfo.Name.Equals(memberName, StringComparison.InvariantCultureIgnoreCase)) { valueMemberInfo.SetValue(instance, value); } } } /// /// Gets the first attribute of type TAttribute decorating the type /// public static TAttribute GetAttribute(this ICustomAttributeProvider type) { return GetTypeAttributes(type).OfType().FirstOrDefault(); } /// /// Gets the first attribute of type TAttribute decorating the type /// public static TAttribute GetAttribute(this ICustomAttributeProvider type, bool inheritable /* = false */) { return GetTypeAttributes(type, inheritable).OfType().FirstOrDefault(); } /// /// Gets the custom attributes of a C# type. /// /// /// public static IEnumerable GetTypeAttributes(ICustomAttributeProvider type) { return GetTypeAttributes(type, false); } /// /// Gets the custom attributes of a C# type. /// public static IEnumerable GetTypeAttributes(ICustomAttributeProvider type, bool inheritable /* = false */) { Throw.IfNull(type, "type"); return type.GetCustomAttributes(inheritable).Cast(); } /// /// Returns true if the given is a numerical type. /// /// /// The complete list of numerical types is: /// /// byte /// sbyte /// short /// ushort /// int /// uint /// long /// ulong /// float /// double /// decimal /// /// /// Type to test for. /// True if the given is a numerical type. False otherwise. public static bool IsNumerical(Type type) { return (IsInteger(type) || IsFloatingPoint(type)); } /// /// Returns true if the given is a floating-point numerical type. /// /// /// The complete list of floating-point numerical types is: /// /// float /// double /// decimal /// /// /// Type to test for. /// True if the given is a floating-point numerical type. False otherwise. public static bool IsFloatingPoint(Type type) { return m_floatingPointTypes.Contains(type); } /// /// Returns true if the given is an integer numerical type. /// /// /// The complete list of integer numerical types is: /// /// byte /// sbyte /// short /// ushort /// int /// uint /// long /// ulong /// /// /// Type to test for. /// True if the given is a integer numerical type. False otherwise. public static bool IsInteger(Type type) { return m_integerTypes.Contains(type); } /// /// Returns true if the given is a signed integer numerical type. /// /// /// public static bool IsSignedInteger(Type type) { return type == typeof(long) || type == typeof(int) || type == typeof(short) || type == typeof(sbyte); } public static bool IsList(Type type) { int found = (from i in type.GetInterfaces() where i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>) select i).Count(); return found > 0; } public static bool IsString(Type type) { return type == typeof(string); } public static object ConvertValue(object value, Type destinationType) { object result; string msg; if (!TryConvertValue(value, destinationType, out result, out msg)) { throw new InvalidCastException(msg); } return result; } public static T ConvertValue(object value) { return (T)ConvertValue(value, typeof(T)); } public static bool TryConvertValue(object value, Type destinationType, out object result, out string errorMsg) { if (TryConvertValue(value, destinationType, out result)) { errorMsg = string.Empty; return true; } errorMsg = $"Cannot convert {value} from {GetUserFriendlyTypeName(value.GetType())} to {GetUserFriendlyTypeName(destinationType)}"; return false; } /// /// Tries to convert from the value to the destination type. Returns false if unable. /// public static bool TryConvertValue(object value, Type destinationType, out object result) { result = value; if (value.GetType() == destinationType) { return true; } if (value is string) { string stringValue = value.ToString(); if (IsInteger(destinationType)) { if (stringValue.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { if (!TryConvertHexStringToInteger(stringValue, destinationType, out value)) { return false; } } else { Int64 intValue; if (Int64.TryParse(stringValue, out intValue)) { value = intValue; } else { return false; } } } else { return TryConvertFromString(stringValue, destinationType, out result); } } value = MatchSign(value, destinationType); try { result = Convert.ChangeType(value, destinationType, CultureInfo.InvariantCulture); return true; } catch { result = null; return false; } } private static bool TryConvertHexStringToInteger(string stringValue, Type destinationType, out object value) { stringValue = stringValue.Substring(2); int size = Marshal.SizeOf(destinationType); switch (size) { case 1: if (!TryParseHexByte(stringValue, out value)) { return false; } break; case 2: if (!TryParseHexUShort(stringValue, out value)) { return false; } break; case 4: if (!TryParseHexUInt(stringValue, out value)) { return false; } break; case 8: if (!TryParseHexULong(stringValue, out value)) { return false; } break; default: throw new NotSupportedException(); } return true; } private static bool TryConvertFromString(string value, Type destinationType, out object convertedValue) { convertedValue = null; if (value == null && destinationType.IsValueType) { return false; } if (destinationType == typeof(int)) { int numberValue; bool result = int.TryParse(value, out numberValue); convertedValue = numberValue; return result; } else if (destinationType == typeof(float)) { float numberValue; bool result = float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out numberValue); convertedValue = numberValue; return result; } TypeConverter converter = TypeConverter.GetConverter(destinationType); if (!converter.CanConvertFrom(typeof(string))) { return false; } try { convertedValue = converter.ConvertFromInvariantString(value); return true; } catch { return false; } } private static bool TryParseHexULong(string hexValue, out object value) { ulong outValue; if (!ulong.TryParse(hexValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out outValue)) { value = null; return false; } value = outValue; return true; } private static bool TryParseHexUInt(string hexValue, out object value) { uint outValue; if (!uint.TryParse(hexValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out outValue)) { value = null; return false; } value = outValue; return true; } private static bool TryParseHexUShort(string hexValue, out object value) { ushort outValue; if (!ushort.TryParse(hexValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out outValue)) { value = null; return false; } value = outValue; return true; } private static bool TryParseHexByte(string hexValue, out object value) { byte outValue; if (!byte.TryParse(hexValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out outValue)) { value = null; return false; } value = outValue; return true; } private static object MatchSign(object value, Type destinationType) { if (value != null && IsInteger(destinationType) && IsInteger(value.GetType())) { bool sourceSigned = IsSignedInteger(value.GetType()); bool targetSigned = IsSignedInteger(destinationType); if (sourceSigned != targetSigned) { value = ConvertSign(value, targetSigned); } } return value; } private static object ConvertSign(object value, bool signed) { Debug.Assert(value != null); int size = Marshal.SizeOf(value.GetType()); unchecked { switch (size) { case 1: if (signed) { return (sbyte)(byte)value; } else { return (byte)(sbyte)value; } case 2: if (signed) { return (short)(ushort)value; } else { return (ushort)(short)value; } case 4: if (signed) { return (int)(uint)value; } else { return (uint)(int)value; } case 8: if (signed) { return (long)(ulong)value; } else { return (ulong)(long)value; } default: throw new NotImplementedException(); } } } private static string GetUserFriendlyTypeName(Type type) { if (type == typeof(string)) { return "an alphanumerical value"; } if (ReflectionHelper.IsNumerical(type)) { return "a numerical value"; } return "a " + type.Name; } #endregion #region Reflection /// /// Gets an attribute on the given enum . /// [NotNull] public static TAttribute Attribute(this Enum element) where TAttribute : Attribute { var result = GetEnumAttribute(element); if (result == null) { string error = $"{typeof(TAttribute).Name} is not defined for {element.GetType().Name}.{element}"; throw new ApplicationException(error); } return result; } /// /// Gets an attribute on the given enum . /// /// /// public static TAttribute GetEnumAttribute(Enum element) where TAttribute : Attribute { Throw.IfNull(element, "element"); FieldInfo fieldInfo = element.GetType().GetField(element.ToString()); if (fieldInfo == null) { return null; } TAttribute[] attributes = (TAttribute[])fieldInfo.GetCustomAttributes(typeof(TAttribute), false); if (attributes.Length > 0) { return attributes[0]; } return null; } /// /// Gets a description for the given . /// /// /// public static string GetEnumDescription(Enum element) { DescriptionAttribute description = GetEnumAttribute(element); return description == null ? element.ToString() : description.Description; } #region Component model /// /// Compares two objects for equality using their browsable properties. /// public static bool AreEqual(object a, object b) { Throw.Assert(a.GetType() == b.GetType()); foreach (ValueMemberInfo valueMemberInfo in GetBrowsableMembers(a.GetType())) { if (!Equals(valueMemberInfo.GetValue(a), valueMemberInfo.GetValue(b))) { return false; } } return true; } private static IEnumerable GetBrowsableMembers(Type type) { foreach (ValueMemberInfo valueMemberInfo in ValueMemberInfo.GetValueMembers(type, true)) { BrowsableAttribute browsableAttribute = valueMemberInfo.GetAttribute(true); if (browsableAttribute != null && browsableAttribute.Browsable) { yield return valueMemberInfo; } } } #endregion #endregion #region String Formatting /// /// Converts the given into a string, for display in the property grid. /// /// public static string FormatStringForPropertyGrid(object value) { if (ReferenceEquals(value, null)) { return string.Empty; } if (value is IFormattable && IsFloatingPoint(value.GetType())) { var convertedValue = ConvertValue(value); if (convertedValue > 0 && convertedValue < 0.00000001) { return "0.00000000"; } if (convertedValue < 0 && convertedValue > -0.00000001) { return "-0.00000000"; } return ((IFormattable)value).ToString("0.########", CultureInfo.InvariantCulture); } return value.ToString(); } #endregion #region Validation #endregion #region Exception Handling /// /// Gets the types contained in the assembly. /// /// /// Returns the result of assembly.GetTypes() but throws a better exception when the types cannot be loaded. /// /// /// [DebuggerNonUserCode] public static Type[] GetTypes(Assembly assembly) { try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException e) { if (e.LoaderExceptions.OfType().Any()) { string msgDependencyMissing = $@"Cannot load assembly {assembly.FullName} because it is missing dependencies. Make sure all dependencies (including unmanaged dlls) are present in the output directory. You can use depends.exe to diagnose this."; throw new AssemblyLoadException(msgDependencyMissing, e); } string msg = $@"Cannot load types for assembly {assembly.FullName}. This is probably due to one of these problems: \tOne of the assembly's dependency is missing. Make sure all dependencies (including unmanaged dlls) are present in the output directory. \tThere is a version mismatch between the assembly and its dependencies. Make sure all assemblies in the output directory are up-to-date (a 'Build Solution' might fix this) You can inspect the LoaderExceptions property on this exception for more information."; throw new AssemblyLoadException(msg, e); } } [Serializable] public class AssemblyLoadException : Exception { #region Constructor public AssemblyLoadException() { } public AssemblyLoadException(string message) : base(message) { } public AssemblyLoadException(string message, Exception inner) : base(message, inner) { } protected AssemblyLoadException( SerializationInfo info, StreamingContext context) : base(info, context) { } #endregion } #endregion #region Lambda magic public static MethodInfo ResolveMethod(Expression expression) { Throw.IfNull(expression, "expression"); Throw.InvalidArgumentIf(!(expression.Body is MethodCallExpression), "Cannot resolve a method from this expression", "expression"); var methodExpression = (MethodCallExpression)expression.Body; return methodExpression.Method; } #endregion #endregion } }