// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.Serialization; using System.Text; using UnityEngine; namespace Animancer { /// Reflection utilities used throughout Animancer. /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerReflection public static class AnimancerReflection { /************************************************************************************************************************/ /// Commonly used combinations. public const BindingFlags AnyAccessBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, InstanceBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, StaticBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; /************************************************************************************************************************/ /// /// Creates a new instance of the `type` using its parameterless constructor if it has one or a fully /// uninitialized object if it doesn't. Or returns null if the . /// public static object CreateDefaultInstance(Type type) { if (type == null || type.IsAbstract) return default; var constructor = type.GetConstructor(InstanceBindings, null, Type.EmptyTypes, null); if (constructor != null) return constructor.Invoke(null); return FormatterServices.GetUninitializedObject(type); } /// /// Creates a using its parameterless constructor if it has one or a fully /// uninitialized object if it doesn't. Or returns null if the . /// public static T CreateDefaultInstance() => (T)CreateDefaultInstance(typeof(T)); /************************************************************************************************************************/ /// [Animancer Extension] /// Returns the first attribute on the `member` /// or null if there is none. /// public static TAttribute GetAttribute( this ICustomAttributeProvider member, bool inherit = false) where TAttribute : class { var type = typeof(TAttribute); return member.IsDefined(type, inherit) ? (TAttribute)member.GetCustomAttributes(type, inherit)[0] : null; } /************************************************************************************************************************/ /// Invokes a method with the specified `methodName` if it exists on the `obj`. [Obfuscation(Exclude = true)]// Obfuscation seems to break IL2CPP Android builds here. public static object TryInvoke( object obj, string methodName, BindingFlags bindings = InstanceBindings | BindingFlags.FlattenHierarchy, Type[] parameterTypes = null, object[] parameters = null) { if (obj == null) return null; parameterTypes ??= Type.EmptyTypes; var method = obj.GetType().GetMethod(methodName, bindings, null, parameterTypes, null); return method?.Invoke(obj, parameters); } /************************************************************************************************************************/ #region Delegates /************************************************************************************************************************/ /// Returns a string describing the details of the `method`. public static string ToStringDetailed( this T method, bool includeType = false) where T : Delegate { var text = StringBuilderPool.Instance.Acquire(); text.AppendDelegate(method, includeType); return text.ReleaseToString(); } /// Appends the details of the `method` to the `text`. public static StringBuilder AppendDelegate( this StringBuilder text, T method, bool includeType = false) where T : Delegate { var type = method != null ? method.GetType() : typeof(T); if (method == null) { return includeType ? text.Append("Null(") .Append(type.GetNameCS()) .Append(')') : text.Append("Null"); } if (includeType) text.Append(type.GetNameCS()) .Append('('); if (method.Target != null) text.Append("Method: "); text.Append(method.Method.DeclaringType.GetNameCS()) .Append('.') .Append(method.Method.Name); if (method.Target != null) text.Append(", Target: '") .Append(method.Target) .Append("'"); if (includeType) text.Append(')'); return text; } /************************************************************************************************************************/ /// Returns the `method`'s DeclaringType.Name. public static string GetFullName(MethodInfo method) => $"{method.DeclaringType.Name}.{method.Name}"; /************************************************************************************************************************/ private static FieldInfo _DelegatesField; private static bool _GotDelegatesField; /// /// Uses reflection to achieve the same as without allocating /// garbage every time. /// /// If the delegate is null or , this method returns false and outputs null. /// If the underlying delegate field was not found, this method returns false and outputs null. /// If the delegate is not multicast, this method this method returns true and outputs null. /// If the delegate is multicast, this method this method returns true and outputs its invocation list. /// /// public static bool TryGetInvocationListNonAlloc(MulticastDelegate multicast, out Delegate[] delegates) { if (multicast == null) { delegates = null; return false; } if (!_GotDelegatesField) { const string FieldName = "delegates"; _GotDelegatesField = true; _DelegatesField = typeof(MulticastDelegate).GetField("delegates", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (_DelegatesField != null && _DelegatesField.FieldType != typeof(Delegate[])) _DelegatesField = null; if (_DelegatesField == null) Debug.LogError($"Unable to find {nameof(MulticastDelegate)}.{FieldName} field."); } if (_DelegatesField == null) { delegates = null; return false; } else { delegates = (Delegate[])_DelegatesField.GetValue(multicast); return true; } } /************************************************************************************************************************/ /// /// Tries to use . /// Otherwise uses the regular . /// public static Delegate[] GetInvocationList(MulticastDelegate multicast) => TryGetInvocationListNonAlloc(multicast, out var delegates) && delegates != null ? delegates : multicast?.GetInvocationList(); /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Type Names /************************************************************************************************************************/ private static readonly Dictionary TypeNames = new() { { typeof(object), "object" }, { typeof(void), "void" }, { typeof(bool), "bool" }, { typeof(byte), "byte" }, { typeof(sbyte), "sbyte" }, { typeof(char), "char" }, { typeof(string), "string" }, { typeof(short), "short" }, { typeof(int), "int" }, { typeof(long), "long" }, { typeof(ushort), "ushort" }, { typeof(uint), "uint" }, { typeof(ulong), "ulong" }, { typeof(float), "float" }, { typeof(double), "double" }, { typeof(decimal), "decimal" }, }; private static readonly Dictionary FullTypeNames = new(TypeNames); /************************************************************************************************************************/ /// Returns the name of the `type` as it would appear in C# code. /// /// Returned values are stored in a dictionary to speed up repeated use. /// /// Example: /// /// typeof(List<float>).FullName would give you: /// /// System.Collections.Generic.List`1[[System.Single, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] /// /// This method would instead return System.Collections.Generic.List<float> if `fullName` is true, or /// just List<float> if it is false. /// public static string GetNameCS(this Type type, bool fullName = true) { if (type == null) return "null"; // Check if we have already got the name for that type. var names = fullName ? FullTypeNames : TypeNames; if (names.TryGetValue(type, out var name)) return name; var text = StringBuilderPool.Instance.Acquire(); if (type.IsArray)// Array = TypeName[]. { text.Append(type.GetElementType().GetNameCS(fullName)); text.Append('['); var dimensions = type.GetArrayRank(); while (dimensions-- > 1) text.Append(','); text.Append(']'); goto Return; } if (type.IsPointer)// Pointer = TypeName*. { text.Append(type.GetElementType().GetNameCS(fullName)); text.Append('*'); goto Return; } if (type.IsGenericParameter)// Generic Parameter = TypeName (for unspecified generic parameters). { text.Append(type.Name); goto Return; } var underlyingType = Nullable.GetUnderlyingType(type); if (underlyingType != null)// Nullable = TypeName != null ? { text.Append(underlyingType.GetNameCS(fullName)); text.Append('?'); goto Return; } // Other Type = Namespace.NestedTypes.TypeName. if (fullName && type.Namespace != null)// Namespace. { text.Append(type.Namespace); text.Append('.'); } var genericArguments = 0; if (type.DeclaringType != null)// Account for Nested Types. { // Count the nesting level. var nesting = 1; var declaringType = type.DeclaringType; while (declaringType.DeclaringType != null) { declaringType = declaringType.DeclaringType; nesting++; } // Append the name of each outer type, starting from the outside. while (nesting-- > 0) { // Walk out to the current nesting level. // This avoids the need to make a list of types in the nest or to insert type names instead of appending them. declaringType = type; for (int i = nesting; i >= 0; i--) declaringType = declaringType.DeclaringType; // Nested Type Name. genericArguments = AppendNameAndGenericArguments(text, declaringType, fullName, genericArguments); text.Append('.'); } } // Type Name. AppendNameAndGenericArguments(text, type, fullName, genericArguments); Return:// Remember and return the name. name = text.ReleaseToString(); names.Add(type, name); return name; } /************************************************************************************************************************/ /// Appends the generic arguments of `type` (after skipping the specified number). public static int AppendNameAndGenericArguments(StringBuilder text, Type type, bool fullName = true, int skipGenericArguments = 0) { var name = type.Name; text.Append(name); if (type.IsGenericType) { var backQuote = name.IndexOf('`'); if (backQuote >= 0) { text.Length -= name.Length - backQuote; var genericArguments = type.GetGenericArguments(); if (skipGenericArguments < genericArguments.Length) { text.Append('<'); var firstArgument = genericArguments[skipGenericArguments]; skipGenericArguments++; if (firstArgument.IsGenericParameter) { while (skipGenericArguments < genericArguments.Length) { text.Append(','); skipGenericArguments++; } } else { text.Append(firstArgument.GetNameCS(fullName)); while (skipGenericArguments < genericArguments.Length) { text.Append(", "); text.Append(genericArguments[skipGenericArguments].GetNameCS(fullName)); skipGenericArguments++; } } text.Append('>'); } } } return skipGenericArguments; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } }