123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 |
- // 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
- {
- /// <summary>Reflection utilities used throughout Animancer.</summary>
- /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerReflection
- public static class AnimancerReflection
- {
- /************************************************************************************************************************/
- /// <summary>Commonly used <see cref="BindingFlags"/> combinations.</summary>
- 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;
- /************************************************************************************************************************/
- /// <summary>
- /// 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 <c>null</c> if the <see cref="Type.IsAbstract"/>.
- /// </summary>
- 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);
- }
- /// <summary>
- /// Creates a <typeparamref name="T"/> using its parameterless constructor if it has one or a fully
- /// uninitialized object if it doesn't. Or returns <c>null</c> if the <see cref="Type.IsAbstract"/>.
- /// </summary>
- public static T CreateDefaultInstance<T>()
- => (T)CreateDefaultInstance(typeof(T));
- /************************************************************************************************************************/
- /// <summary>[Animancer Extension]
- /// Returns the first <typeparamref name="TAttribute"/> attribute on the `member`
- /// or <c>null</c> if there is none.
- /// </summary>
- public static TAttribute GetAttribute<TAttribute>(
- 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;
- }
- /************************************************************************************************************************/
- /// <summary>Invokes a method with the specified `methodName` if it exists on the `obj`.</summary>
- [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
- /************************************************************************************************************************/
- /// <summary>Returns a string describing the details of the `method`.</summary>
- public static string ToStringDetailed<T>(
- this T method,
- bool includeType = false)
- where T : Delegate
- {
- var text = StringBuilderPool.Instance.Acquire();
- text.AppendDelegate(method, includeType);
- return text.ReleaseToString();
- }
- /// <summary>Appends the details of the `method` to the `text`.</summary>
- public static StringBuilder AppendDelegate<T>(
- 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;
- }
- /************************************************************************************************************************/
- /// <summary>Returns the `method`'s <c>DeclaringType.Name</c>.</summary>
- public static string GetFullName(MethodInfo method)
- => $"{method.DeclaringType.Name}.{method.Name}";
- /************************************************************************************************************************/
- private static FieldInfo _DelegatesField;
- private static bool _GotDelegatesField;
- /// <summary>
- /// Uses reflection to achieve the same as <see cref="Delegate.GetInvocationList"/> without allocating
- /// garbage every time.
- /// <list type="bullet">
- /// <item>If the delegate is <c>null</c> or , this method returns <c>false</c> and outputs <c>null</c>.</item>
- /// <item>If the underlying <c>delegate</c> field was not found, this method returns <c>false</c> and outputs <c>null</c>.</item>
- /// <item>If the delegate is not multicast, this method this method returns <c>true</c> and outputs <c>null</c>.</item>
- /// <item>If the delegate is multicast, this method this method returns <c>true</c> and outputs its invocation list.</item>
- /// </list>
- /// </summary>
- 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;
- }
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Tries to use <see cref="TryGetInvocationListNonAlloc"/>.
- /// Otherwise uses the regular <see cref="MulticastDelegate.GetInvocationList"/>.
- /// </summary>
- public static Delegate[] GetInvocationList(MulticastDelegate multicast)
- => TryGetInvocationListNonAlloc(multicast, out var delegates) && delegates != null
- ? delegates
- : multicast?.GetInvocationList();
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Type Names
- /************************************************************************************************************************/
- private static readonly Dictionary<Type, string>
- 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<Type, string>
- FullTypeNames = new(TypeNames);
- /************************************************************************************************************************/
- /// <summary>Returns the name of the `type` as it would appear in C# code.</summary>
- /// <remarks>
- /// Returned values are stored in a dictionary to speed up repeated use.
- /// <para></para>
- /// <strong>Example:</strong>
- /// <para></para>
- /// <c>typeof(List<float>).FullName</c> would give you:
- /// <para></para>
- /// <c>System.Collections.Generic.List`1[[System.Single, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]</c>
- /// <para></para>
- /// This method would instead return <c>System.Collections.Generic.List<float></c> if `fullName` is <c>true</c>, or
- /// just <c>List<float></c> if it is <c>false</c>.
- /// </remarks>
- 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<GenericArguments>.
- 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;
- }
- /************************************************************************************************************************/
- /// <summary>Appends the generic arguments of `type` (after skipping the specified number).</summary>
- 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
- /************************************************************************************************************************/
- }
- }
|