JD2022-TU1/main/tools/framework/JD.Reflection/ReflectionHelper.cs

716 lines
23 KiB
C#

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
{
/// <summary>
/// Helper class.
/// </summary>
public static class ReflectionHelper
{
#region Variables
private static readonly List<Type> m_integerTypes = new List<Type>();
private static readonly List<Type> m_floatingPointTypes = new List<Type>();
private static readonly TypeConverterCache TypeConverter = new TypeConverterCache();
#endregion
#region Constructor
/// <summary>
/// Static constructor.
/// </summary>
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
/// <summary>
/// Set a value of a member via reflection
/// </summary>
/// <param name="instance"></param>
/// <param name="memberName"></param>
/// <param name="value"></param>
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);
}
}
}
/// <summary>
/// Gets the first attribute of type TAttribute decorating the type <paramref name="type"/>
/// </summary>
public static TAttribute GetAttribute<TAttribute>(this ICustomAttributeProvider type)
{
return GetTypeAttributes(type).OfType<TAttribute>().FirstOrDefault();
}
/// <summary>
/// Gets the first attribute of type TAttribute decorating the type <paramref name="type"/>
/// </summary>
public static TAttribute GetAttribute<TAttribute>(this ICustomAttributeProvider type, bool inheritable /* = false */)
{
return GetTypeAttributes(type, inheritable).OfType<TAttribute>().FirstOrDefault();
}
/// <summary>
/// Gets the custom attributes of a C# type.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IEnumerable<Attribute> GetTypeAttributes(ICustomAttributeProvider type)
{
return GetTypeAttributes(type, false);
}
/// <summary>
/// Gets the custom attributes of a C# type.
/// </summary>
public static IEnumerable<Attribute> GetTypeAttributes(ICustomAttributeProvider type, bool inheritable /* = false */)
{
Throw.IfNull(type, "type");
return type.GetCustomAttributes(inheritable).Cast<Attribute>();
}
/// <summary>
/// Returns true if the given <paramref name="type"/> is a numerical type.
/// </summary>
/// <remarks>
/// The complete list of numerical types is:
/// <list type="bullet">
/// <item>byte</item>
/// <item>sbyte</item>
/// <item>short</item>
/// <item>ushort</item>
/// <item>int</item>
/// <item>uint</item>
/// <item>long</item>
/// <item>ulong</item>
/// <item>float</item>
/// <item>double</item>
/// <item>decimal</item>
/// </list>
/// </remarks>
/// <param name="type">Type to test for.</param>
/// <returns>True if the given <paramref name="type"/> is a numerical type. False otherwise.</returns>
public static bool IsNumerical(Type type)
{
return (IsInteger(type) || IsFloatingPoint(type));
}
/// <summary>
/// Returns true if the given <paramref name="type"/> is a floating-point numerical type.
/// </summary>
/// <remarks>
/// The complete list of floating-point numerical types is:
/// <list type="bullet">
/// <item>float</item>
/// <item>double</item>
/// <item>decimal</item>
/// </list>
/// </remarks>
/// <param name="type">Type to test for.</param>
/// <returns>True if the given <paramref name="type"/> is a floating-point numerical type. False otherwise.</returns>
public static bool IsFloatingPoint(Type type)
{
return m_floatingPointTypes.Contains(type);
}
/// <summary>
/// Returns true if the given <paramref name="type"/> is an integer numerical type.
/// </summary>
/// <remarks>
/// The complete list of integer numerical types is:
/// <list type="bullet">
/// <item>byte</item>
/// <item>sbyte</item>
/// <item>short</item>
/// <item>ushort</item>
/// <item>int</item>
/// <item>uint</item>
/// <item>long</item>
/// <item>ulong</item>
/// </list>
/// </remarks>
/// <param name="type">Type to test for.</param>
/// <returns>True if the given <paramref name="type"/> is a integer numerical type. False otherwise.</returns>
public static bool IsInteger(Type type)
{
return m_integerTypes.Contains(type);
}
/// <summary>
/// Returns true if the given <paramref name="type"/> is a signed integer numerical type.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
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<T>(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;
}
/// <summary>
/// Tries to convert from the value to the destination type. Returns false if unable.
/// </summary>
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
/// <summary>
/// Gets an attribute on the given enum <paramref name="element"/>.
/// </summary>
[NotNull]
public static TAttribute Attribute<TAttribute>(this Enum element)
where TAttribute : Attribute
{
var result = GetEnumAttribute<TAttribute>(element);
if (result == null)
{
string error = $"{typeof(TAttribute).Name} is not defined for {element.GetType().Name}.{element}";
throw new ApplicationException(error);
}
return result;
}
/// <summary>
/// Gets an attribute on the given enum <paramref name="element"/>.
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
public static TAttribute GetEnumAttribute<TAttribute>(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;
}
/// <summary>
/// Gets a description for the given <paramref name="element"/>.
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
public static string GetEnumDescription(Enum element)
{
DescriptionAttribute description = GetEnumAttribute<DescriptionAttribute>(element);
return description == null ? element.ToString() : description.Description;
}
#region Component model
/// <summary>
/// Compares two objects for equality using their browsable properties.
/// </summary>
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<ValueMemberInfo> GetBrowsableMembers(Type type)
{
foreach (ValueMemberInfo valueMemberInfo in ValueMemberInfo.GetValueMembers(type, true))
{
BrowsableAttribute browsableAttribute = valueMemberInfo.GetAttribute<BrowsableAttribute>(true);
if (browsableAttribute != null && browsableAttribute.Browsable)
{
yield return valueMemberInfo;
}
}
}
#endregion
#endregion
#region String Formatting
/// <summary>
/// Converts the given <paramref name="value"/> into a string, for display in the property grid.
/// </summary>
/// <param name="value"></param>
public static string FormatStringForPropertyGrid(object value)
{
if (ReferenceEquals(value, null))
{
return string.Empty;
}
if (value is IFormattable && IsFloatingPoint(value.GetType()))
{
var convertedValue = ConvertValue<double>(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
/// <summary>
/// Gets the types contained in the assembly.
/// </summary>
/// <remarks>
/// Returns the result of assembly.GetTypes() but throws a better exception when the types cannot be loaded.
/// </remarks>
/// <param name="assembly"></param>
/// <returns></returns>
[DebuggerNonUserCode]
public static Type[] GetTypes(Assembly assembly)
{
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
if (e.LoaderExceptions.OfType<FileNotFoundException>().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<Action> 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
}
}