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 _typeDisplayNames = new Dictionary { { 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 /// Determines if a type is a delegate. /// If the type is a delegate. public static bool IsDelegate(this Type type) { if (!typeof(Delegate).IsAssignableFrom(type)) { return false; } return true; } /// Determines if a type is a strongly typed delegate. /// If the type is a strongly typed delegate. public static bool IsStrongDelegate(this Type type) { if (!type.IsDelegate()) { return false; } if (type.IsAbstract) { return false; } return true; } /// Determines if a field is a delegate. /// If the field is a delegate. public static bool IsDelegate(this FieldInfo fieldInfo) { return fieldInfo.FieldType.IsDelegate(); } /// Determines if a field is a strongly typed delegate. /// The field to query. /// If the field is a strongly typed delegate. public static bool IsStrongDelegate(this FieldInfo fieldInfo) { return fieldInfo.FieldType.IsStrongDelegate(); } /// /// Determines if the type is a generic type of the given non-generic type. /// /// The non-generic type to test against. /// If the type is a generic type of the non-generic type. public static bool IsGenericTypeOf(this Type genericType, Type nonGenericType) { if (!genericType.IsGenericType) { return false; } return genericType.GetGenericTypeDefinition() == nonGenericType; } /// /// Determines if the type is a derived type of the given base type. /// /// The base type to test against. /// If the type is a derived type of the base type. public static bool IsDerivedTypeOf(this Type type, Type baseType) { return baseType.IsAssignableFrom(type); } /// /// Determines if an object the given type can be casted to the specified type. /// /// The destination type of the cast. /// If only implicit casts should be considered. /// If the cast can be performed. 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 lowerTypes = Enumerable.Empty(); 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 baseType, Func 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))); } /// /// Dynamically casts an object to the specified type. /// /// The destination type of the cast. /// The object to cast. /// The dynamically casted object. 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); } } /// Determines if the given method is an override. /// If the method is an override. public static bool IsOverride(this MethodInfo methodInfo) { return methodInfo.GetBaseDefinition().DeclaringType != methodInfo.DeclaringType; } /// /// Gets if the provider has the specified attribute. /// /// The attribute to test. /// The attribute provider. /// If base declarations should be searched. /// If the attribute is present. public static bool HasAttribute(this ICustomAttributeProvider provider, bool searchInherited = true) where T : Attribute { try { return provider.IsDefined(typeof(T), searchInherited); } catch (MissingMethodException) { return false; } } /// /// Gets a formatted display name for a given type. /// /// The type to generate a display name for. /// If the namespace should be included when generating the typename. /// The generated display name. 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 parts = type .GetGenericArguments() .Select(x => x.GetDisplayName(includeNamespace)); return $"({string.Join(", ", parts)})"; } /// /// Determines if two methods from different types have the same signature. /// /// First method /// Second method /// true if they are equal 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; } /// /// Rebases a method onto a new type by finding the corresponding method with an equal signature. /// /// Method to rebase /// New type to rebase the method onto /// The rebased method 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]; } } }