370 lines
14 KiB
C#
370 lines
14 KiB
C#
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Linq;
|
|||
|
using System.Linq.Expressions;
|
|||
|
using System.Reflection;
|
|||
|
|
|||
|
namespace QFSW.QC.Utilities
|
|||
|
{
|
|||
|
public static class ReflectionExtensions
|
|||
|
{
|
|||
|
#region Lookup Tables
|
|||
|
private static readonly Dictionary<Type, string> _typeDisplayNames = new Dictionary<Type, string>
|
|||
|
{
|
|||
|
{ typeof(int), "int" },
|
|||
|
{ typeof(float), "float" },
|
|||
|
{ typeof(decimal), "decimal" },
|
|||
|
{ typeof(double), "double" },
|
|||
|
{ typeof(string), "string" },
|
|||
|
{ typeof(bool), "bool" },
|
|||
|
{ typeof(byte), "byte" },
|
|||
|
{ typeof(sbyte), "sbyte" },
|
|||
|
{ typeof(uint), "uint" },
|
|||
|
{ typeof(short), "short" },
|
|||
|
{ typeof(ushort), "ushort" },
|
|||
|
{ typeof(long), "decimal" },
|
|||
|
{ typeof(ulong), "ulong" },
|
|||
|
{ typeof(char), "char" },
|
|||
|
{ typeof(object), "object" }
|
|||
|
};
|
|||
|
|
|||
|
private static readonly Type[] _valueTupleTypes =
|
|||
|
{
|
|||
|
typeof(ValueTuple<>),
|
|||
|
typeof(ValueTuple<,>),
|
|||
|
typeof(ValueTuple<,,>),
|
|||
|
typeof(ValueTuple<,,,>),
|
|||
|
typeof(ValueTuple<,,,,>),
|
|||
|
typeof(ValueTuple<,,,,,>),
|
|||
|
typeof(ValueTuple<,,,,,,>),
|
|||
|
typeof(ValueTuple<,,,,,,,>)
|
|||
|
};
|
|||
|
|
|||
|
private static readonly Type[][] _primitiveTypeCastHierarchy =
|
|||
|
{
|
|||
|
new[] { typeof(byte), typeof(sbyte), typeof(char) },
|
|||
|
new[] { typeof(short), typeof(ushort) },
|
|||
|
new[] { typeof(int), typeof(uint) },
|
|||
|
new[] { typeof(long), typeof(ulong) },
|
|||
|
new[] { typeof(float) },
|
|||
|
new[] { typeof(double) }
|
|||
|
};
|
|||
|
#endregion
|
|||
|
|
|||
|
/// <summary>Determines if a type is a delegate.</summary>
|
|||
|
/// <returns>If the type is a delegate.</returns>
|
|||
|
public static bool IsDelegate(this Type type)
|
|||
|
{
|
|||
|
if (!typeof(Delegate).IsAssignableFrom(type)) { return false; }
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>Determines if a type is a strongly typed delegate.</summary>
|
|||
|
/// <returns>If the type is a strongly typed delegate.</returns>
|
|||
|
public static bool IsStrongDelegate(this Type type)
|
|||
|
{
|
|||
|
if (!type.IsDelegate()) { return false; }
|
|||
|
if (type.IsAbstract) { return false; }
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>Determines if a field is a delegate.</summary>
|
|||
|
/// <returns>If the field is a delegate.</returns>
|
|||
|
public static bool IsDelegate(this FieldInfo fieldInfo)
|
|||
|
{
|
|||
|
return fieldInfo.FieldType.IsDelegate();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>Determines if a field is a strongly typed delegate.</summary>
|
|||
|
/// <param name="fieldInfo">The field to query.</param>
|
|||
|
/// <returns>If the field is a strongly typed delegate.</returns>
|
|||
|
public static bool IsStrongDelegate(this FieldInfo fieldInfo)
|
|||
|
{
|
|||
|
return fieldInfo.FieldType.IsStrongDelegate();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Determines if the type is a generic type of the given non-generic type.
|
|||
|
/// </summary>
|
|||
|
/// <param name="nonGenericType">The non-generic type to test against.</param>
|
|||
|
/// <returns>If the type is a generic type of the non-generic type.</returns>
|
|||
|
public static bool IsGenericTypeOf(this Type genericType, Type nonGenericType)
|
|||
|
{
|
|||
|
if (!genericType.IsGenericType) { return false; }
|
|||
|
return genericType.GetGenericTypeDefinition() == nonGenericType;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Determines if the type is a derived type of the given base type.
|
|||
|
/// </summary>
|
|||
|
/// <param name="baseType">The base type to test against.</param>
|
|||
|
/// <returns>If the type is a derived type of the base type.</returns>
|
|||
|
public static bool IsDerivedTypeOf(this Type type, Type baseType)
|
|||
|
{
|
|||
|
return baseType.IsAssignableFrom(type);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Determines if an object the given type can be casted to the specified type.
|
|||
|
/// </summary>
|
|||
|
/// <param name="to">The destination type of the cast.</param>
|
|||
|
/// <param name="implicitly">If only implicit casts should be considered.</param>
|
|||
|
/// <returns>If the cast can be performed.</returns>
|
|||
|
public static bool IsCastableTo(this Type from, Type to, bool implicitly = false)
|
|||
|
{
|
|||
|
return to.IsAssignableFrom(from) || from.HasCastDefined(to, implicitly);
|
|||
|
}
|
|||
|
|
|||
|
private static bool HasCastDefined(this Type from, Type to, bool implicitly)
|
|||
|
{
|
|||
|
if ((from.IsPrimitive || from.IsEnum) && (to.IsPrimitive || to.IsEnum))
|
|||
|
{
|
|||
|
if (!implicitly)
|
|||
|
{
|
|||
|
return from == to || (from != typeof(bool) && to != typeof(bool));
|
|||
|
}
|
|||
|
|
|||
|
IEnumerable<Type> lowerTypes = Enumerable.Empty<Type>();
|
|||
|
foreach (Type[] types in _primitiveTypeCastHierarchy)
|
|||
|
{
|
|||
|
if (types.Any(t => t == to))
|
|||
|
{
|
|||
|
return lowerTypes.Any(t => t == from);
|
|||
|
}
|
|||
|
|
|||
|
lowerTypes = lowerTypes.Concat(types);
|
|||
|
}
|
|||
|
|
|||
|
return false; // IntPtr, UIntPtr, Enum, Boolean
|
|||
|
}
|
|||
|
|
|||
|
return IsCastDefined(to, m => m.GetParameters()[0].ParameterType, _ => from, implicitly, false)
|
|||
|
|| IsCastDefined(from, _ => to, m => m.ReturnType, implicitly, true);
|
|||
|
}
|
|||
|
|
|||
|
private static bool IsCastDefined(Type type, Func<MethodInfo, Type> baseType, Func<MethodInfo, Type> derivedType, bool implicitly, bool lookInBase)
|
|||
|
{
|
|||
|
BindingFlags flags = BindingFlags.Public | BindingFlags.Static | (lookInBase ? BindingFlags.FlattenHierarchy : BindingFlags.DeclaredOnly);
|
|||
|
MethodInfo[] methods = type.GetMethods(flags);
|
|||
|
|
|||
|
return methods.Where(m => m.Name == "op_Implicit" || (!implicitly && m.Name == "op_Explicit"))
|
|||
|
.Any(m => baseType(m).IsAssignableFrom(derivedType(m)));
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Dynamically casts an object to the specified type.
|
|||
|
/// </summary>
|
|||
|
/// <param name="type">The destination type of the cast.</param>
|
|||
|
/// <param name="data">The object to cast.</param>
|
|||
|
/// <returns>The dynamically casted object.</returns>
|
|||
|
public static object Cast(this Type type, object data)
|
|||
|
{
|
|||
|
if (type.IsInstanceOfType(data))
|
|||
|
{
|
|||
|
return data;
|
|||
|
}
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
return Convert.ChangeType(data, type);
|
|||
|
}
|
|||
|
catch (InvalidCastException)
|
|||
|
{
|
|||
|
Type srcType = data.GetType();
|
|||
|
ParameterExpression dataParam = Expression.Parameter(srcType, "data");
|
|||
|
Expression body = Expression.Convert(Expression.Convert(dataParam, srcType), type);
|
|||
|
|
|||
|
Delegate run = Expression.Lambda(body, dataParam).Compile();
|
|||
|
return run.DynamicInvoke(data);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>Determines if the given method is an override.</summary>
|
|||
|
/// <returns>If the method is an override.</returns>
|
|||
|
public static bool IsOverride(this MethodInfo methodInfo)
|
|||
|
{
|
|||
|
return methodInfo.GetBaseDefinition().DeclaringType != methodInfo.DeclaringType;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets if the provider has the specified attribute.
|
|||
|
/// </summary>
|
|||
|
/// <typeparam name="T">The attribute to test.</typeparam>
|
|||
|
/// <param name="provider">The attribute provider.</param>
|
|||
|
/// <param name="searchInherited">If base declarations should be searched.</param>
|
|||
|
/// <returns>If the attribute is present.</returns>
|
|||
|
public static bool HasAttribute<T>(this ICustomAttributeProvider provider, bool searchInherited = true) where T : Attribute
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
return provider.IsDefined(typeof(T), searchInherited);
|
|||
|
}
|
|||
|
catch (MissingMethodException)
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets a formatted display name for a given type.
|
|||
|
/// </summary>
|
|||
|
/// <param name="type">The type to generate a display name for.</param>
|
|||
|
/// <param name="includeNamespace">If the namespace should be included when generating the typename.</param>
|
|||
|
/// <returns>The generated display name.</returns>
|
|||
|
public static string GetDisplayName(this Type type, bool includeNamespace = false)
|
|||
|
{
|
|||
|
if (type.IsArray)
|
|||
|
{
|
|||
|
int rank = type.GetArrayRank();
|
|||
|
string innerTypeName = GetDisplayName(type.GetElementType(), includeNamespace);
|
|||
|
return $"{innerTypeName}[{new string(',', rank - 1)}]";
|
|||
|
}
|
|||
|
|
|||
|
if (_typeDisplayNames.ContainsKey(type))
|
|||
|
{
|
|||
|
string baseName = _typeDisplayNames[type];
|
|||
|
if (type.IsGenericType && !type.IsConstructedGenericType)
|
|||
|
{
|
|||
|
Type[] genericArgs = type.GetGenericArguments();
|
|||
|
return $"{baseName}<{new string(',', genericArgs.Length - 1)}>";
|
|||
|
}
|
|||
|
|
|||
|
return baseName;
|
|||
|
}
|
|||
|
|
|||
|
if (type.IsGenericType)
|
|||
|
{
|
|||
|
Type baseType = type.GetGenericTypeDefinition();
|
|||
|
Type[] genericArgs = type.GetGenericArguments();
|
|||
|
|
|||
|
if (_valueTupleTypes.Contains(baseType))
|
|||
|
{
|
|||
|
return GetTupleDisplayName(type, includeNamespace);
|
|||
|
}
|
|||
|
|
|||
|
if (type.IsConstructedGenericType)
|
|||
|
{
|
|||
|
string[] genericNames = new string[genericArgs.Length];
|
|||
|
for (int i = 0; i < genericArgs.Length; i++)
|
|||
|
{
|
|||
|
genericNames[i] = GetDisplayName(genericArgs[i], includeNamespace);
|
|||
|
}
|
|||
|
|
|||
|
string baseName = GetDisplayName(baseType, includeNamespace).Split('<')[0];
|
|||
|
return $"{baseName}<{string.Join(", ", genericNames)}>";
|
|||
|
}
|
|||
|
|
|||
|
string typeName = includeNamespace
|
|||
|
? type.FullName
|
|||
|
: type.Name;
|
|||
|
|
|||
|
return $"{typeName.Split('`')[0]}<{new string(',', genericArgs.Length - 1)}>";
|
|||
|
}
|
|||
|
|
|||
|
Type declaringType = type.DeclaringType;
|
|||
|
if (declaringType != null)
|
|||
|
{
|
|||
|
string declaringName = GetDisplayName(declaringType, includeNamespace);
|
|||
|
return $"{declaringName}.{type.Name}";
|
|||
|
}
|
|||
|
|
|||
|
return includeNamespace
|
|||
|
? type.FullName
|
|||
|
: type.Name;
|
|||
|
}
|
|||
|
|
|||
|
private static string GetTupleDisplayName(this Type type, bool includeNamespace = false)
|
|||
|
{
|
|||
|
IEnumerable<string> parts = type
|
|||
|
.GetGenericArguments()
|
|||
|
.Select(x => x.GetDisplayName(includeNamespace));
|
|||
|
|
|||
|
return $"({string.Join(", ", parts)})";
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Determines if two methods from different types have the same signature.
|
|||
|
/// </summary>
|
|||
|
/// <param name="a">First method</param>
|
|||
|
/// <param name="b">Second method</param>
|
|||
|
/// <returns><c>true</c> if they are equal</returns>
|
|||
|
public static bool AreMethodsEqual(MethodInfo a, MethodInfo b)
|
|||
|
{
|
|||
|
if (a.Name != b.Name) return false;
|
|||
|
|
|||
|
ParameterInfo[] paramsA = a.GetParameters();
|
|||
|
ParameterInfo[] paramsB = b.GetParameters();
|
|||
|
|
|||
|
if (paramsA.Length != paramsB.Length) return false;
|
|||
|
for (int i = 0; i < paramsA.Length; i++)
|
|||
|
{
|
|||
|
ParameterInfo pa = paramsA[i];
|
|||
|
ParameterInfo pb = paramsB[i];
|
|||
|
|
|||
|
if (pa.Name != pb.Name) return false;
|
|||
|
if (pa.HasDefaultValue != pb.HasDefaultValue) return false;
|
|||
|
|
|||
|
Type ta = pa.ParameterType;
|
|||
|
Type tb = pb.ParameterType;
|
|||
|
|
|||
|
if (!ta.ContainsGenericParameters && !tb.ContainsGenericParameters)
|
|||
|
{
|
|||
|
if (ta != tb) return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (a.IsGenericMethod != b.IsGenericMethod) return false;
|
|||
|
if (a.IsGenericMethod && b.IsGenericMethod)
|
|||
|
{
|
|||
|
Type[] genericA = a.GetGenericArguments();
|
|||
|
Type[] genericB = b.GetGenericArguments();
|
|||
|
|
|||
|
if (genericA.Length != genericB.Length) return false;
|
|||
|
for (int i = 0; i < genericA.Length; i++)
|
|||
|
{
|
|||
|
Type ga = genericA[i];
|
|||
|
Type gb = genericB[i];
|
|||
|
|
|||
|
if (ga.Name != gb.Name) return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Rebases a method onto a new type by finding the corresponding method with an equal signature.
|
|||
|
/// </summary>
|
|||
|
/// <param name="method">Method to rebase</param>
|
|||
|
/// <param name="newBase">New type to rebase the method onto</param>
|
|||
|
/// <returns>The rebased method</returns>
|
|||
|
public static MethodInfo RebaseMethod(this MethodInfo method, Type newBase)
|
|||
|
{
|
|||
|
BindingFlags flags = BindingFlags.Default;
|
|||
|
|
|||
|
flags |= method.IsStatic
|
|||
|
? BindingFlags.Static
|
|||
|
: BindingFlags.Instance;
|
|||
|
|
|||
|
flags |= method.IsPublic
|
|||
|
? BindingFlags.Public
|
|||
|
: BindingFlags.NonPublic;
|
|||
|
|
|||
|
MethodInfo[] candidates = newBase.GetMethods(flags)
|
|||
|
.Where(x => AreMethodsEqual(x, method))
|
|||
|
.ToArray();
|
|||
|
|
|||
|
if (candidates.Length == 0)
|
|||
|
{
|
|||
|
throw new ArgumentException($"Could not rebase method {method} onto type {newBase} as no matching candidates were found");
|
|||
|
}
|
|||
|
|
|||
|
if (candidates.Length > 1)
|
|||
|
{
|
|||
|
throw new ArgumentException($"Could not rebase method {method} onto type {newBase} as too many matching candidates were found");
|
|||
|
}
|
|||
|
|
|||
|
return candidates[0];
|
|||
|
}
|
|||
|
}
|
|||
|
}
|