716 lines
23 KiB
C#
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
|
|
}
|
|
}
|