Last active
January 27, 2022 03:32
-
-
Save Mikilo/8edbf562d7d3d39867bba1c4687240e5 to your computer and use it in GitHub Desktop.
CSharpMeta
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Reflection; | |
using System.Reflection.Emit; | |
using System.Runtime.CompilerServices; | |
namespace NGToolsEditor | |
{ | |
public static class CSharpMeta | |
{ | |
public abstract class MemberHandler | |
{ | |
public readonly object target; | |
protected MemberHandler(object target) | |
{ | |
this.target = target; | |
} | |
} | |
public abstract class MemberHandler<T> : MemberHandler | |
{ | |
public readonly T memberInfo; | |
protected MemberHandler(object target, T member) : base(target) | |
{ | |
this.memberInfo = member; | |
} | |
public override string ToString() | |
{ | |
return this.target + " " + this.memberInfo; | |
} | |
} | |
public class EventHandler : MemberHandler<EventInfo> | |
{ | |
public EventHandler(object target, EventInfo member) : base(target, member) | |
{ | |
} | |
public void Add(Delegate callback) | |
{ | |
CSharpMeta.AddEvent(target, this.memberInfo, callback); | |
} | |
} | |
public class FieldHandler : MemberHandler<FieldInfo> | |
{ | |
public FieldHandler(object target, FieldInfo member) : base(target, member) | |
{ | |
} | |
public void Add(Delegate callback) | |
{ | |
CSharpMeta.AddDelegate(this.target, this.memberInfo, callback); | |
} | |
} | |
public class PropertyHandler : MemberHandler<PropertyInfo> | |
{ | |
public PropertyHandler(object target, PropertyInfo member) : base(target, member) | |
{ | |
} | |
} | |
public static T Get<T>(object target, string path) | |
{ | |
return (T)CSharpMeta.GetFieldOrPropertyValue(target, path.Split('.'), 0); | |
} | |
public static T Get<T>(Type type, string path) | |
{ | |
return CSharpMeta.GetFieldOrPropertyValue<T>(type, path.Split('.'), 0); | |
} | |
public static T Get<T>(string path) | |
{ | |
Type type = CSharpMeta.ResolveType(path); | |
if (type != null) | |
return CSharpMeta.GetFieldOrPropertyValue<T>(type, path.Split('.'), type.FullName.Split('.').Length); | |
return default(T); | |
} | |
public static void Add<T1, T2, T3, T4>(object target, string path, Action<T1, T2, T3, T4> callback) | |
{ | |
CSharpMeta.ResolveDelegate(target, path, callback); | |
} | |
public static void Add<T1, T2, T3>(object target, string path, Action<T1, T2, T3> callback) | |
{ | |
CSharpMeta.ResolveDelegate(target, path, callback); | |
} | |
public static void Add<T1, T2>(object target, string path, Action<T1, T2> callback) | |
{ | |
CSharpMeta.ResolveDelegate(target, path, callback); | |
} | |
public static void Add<T1>(object target, string path, Action<T1> callback) | |
{ | |
CSharpMeta.ResolveDelegate(target, path, callback); | |
} | |
public static void Add(object target, string path, Action callback) | |
{ | |
CSharpMeta.ResolveDelegate(target, path, callback); | |
} | |
public static void Add<T1, T2, T3, T4>(Type type, string path, Action<T1, T2, T3, T4> callback) | |
{ | |
CSharpMeta.ResolveDelegate(type, path, callback); | |
} | |
public static void Add<T1, T2, T3>(Type type, string path, Action<T1, T2, T3> callback) | |
{ | |
CSharpMeta.ResolveDelegate(type, path, callback); | |
} | |
public static void Add<T1, T2>(Type type, string path, Action<T1, T2> callback) | |
{ | |
CSharpMeta.ResolveDelegate(type, path, callback); | |
} | |
public static void Add<T1>(Type type, string path, Action<T1> callback) | |
{ | |
CSharpMeta.ResolveDelegate(type, path, callback); | |
} | |
public static void Add(Type type, string path, Action callback) | |
{ | |
CSharpMeta.ResolveDelegate(type, path, callback); | |
} | |
public static void Add<T1, T2, T3, T4>(string path, Action<T1, T2, T3, T4> callback) | |
{ | |
CSharpMeta.ResolveDelegate(path, callback); | |
} | |
public static void Add<T1, T2, T3>(string path, Action<T1, T2, T3> callback) | |
{ | |
CSharpMeta.ResolveDelegate(path, callback); | |
} | |
public static void Add<T1, T2>(string path, Action<T1, T2> callback) | |
{ | |
CSharpMeta.ResolveDelegate(path, callback); | |
} | |
public static void Add<T1>(string path, Action<T1> callback) | |
{ | |
CSharpMeta.ResolveDelegate(path, callback); | |
} | |
public static void Add(string path, Action callback) | |
{ | |
CSharpMeta.ResolveDelegate(path, callback); | |
} | |
public static void Add<T1, T2, T3, T4, R>(string path, Func<T1, T2, T3, T4, R> callback) | |
{ | |
CSharpMeta.ResolveDelegate(path, callback); | |
} | |
public static void Add<T1, T2, T3, R>(string path, Func<T1, T2, T3, R> callback) | |
{ | |
CSharpMeta.ResolveDelegate(path, callback); | |
} | |
public static void Add<T1, T2, R>(string path, Func<T1, T2, R> callback) | |
{ | |
CSharpMeta.ResolveDelegate(path, callback); | |
} | |
public static void Add<T1, R>(string path, Func<T1, R> callback) | |
{ | |
CSharpMeta.ResolveDelegate(path, callback); | |
} | |
public static void Add<R>(string path, Func<R> callback) | |
{ | |
CSharpMeta.ResolveDelegate(path, callback); | |
} | |
public static void Add<T1, T2, T3, T4, R>(object target, string path, Func<T1, T2, T3, T4, R> callback) | |
{ | |
CSharpMeta.ResolveDelegate(target, path, callback); | |
} | |
public static void Add<T1, T2, T3, R>(object target, string path, Func<T1, T2, T3, R> callback) | |
{ | |
CSharpMeta.ResolveDelegate(target, path, callback); | |
} | |
public static void Add<T1, T2, R>(object target, string path, Func<T1, T2, R> callback) | |
{ | |
CSharpMeta.ResolveDelegate(target, path, callback); | |
} | |
public static void Add<T1, R>(object target, string path, Func<T1, R> callback) | |
{ | |
CSharpMeta.ResolveDelegate(target, path, callback); | |
} | |
public static void Add<R>(object target, string path, Func<R> callback) | |
{ | |
CSharpMeta.ResolveDelegate(target, path, callback); | |
} | |
public static void Add<T1, T2, T3, T4, R>(Type type, string path, Func<T1, T2, T3, T4, R> callback) | |
{ | |
CSharpMeta.ResolveDelegate(type, path, callback); | |
} | |
public static void Add<T1, T2, T3, R>(Type type, string path, Func<T1, T2, T3, R> callback) | |
{ | |
CSharpMeta.ResolveDelegate(type, path, callback); | |
} | |
public static void Add<T1, T2, R>(Type type, string path, Func<T1, T2, R> callback) | |
{ | |
CSharpMeta.ResolveDelegate(type, path, callback); | |
} | |
public static void Add<T1, R>(Type type, string path, Func<T1, R> callback) | |
{ | |
CSharpMeta.ResolveDelegate(type, path, callback); | |
} | |
public static void Add<R>(Type type, string path, Func<R> callback) | |
{ | |
CSharpMeta.ResolveDelegate(type, path, callback); | |
} | |
public static void Add<T>(object target, string path, T callback) where T : Delegate | |
{ | |
CSharpMeta.ResolveDelegate(target, path, callback); | |
} | |
public static void Add<T>(Type type, string path, T callback) where T : Delegate | |
{ | |
CSharpMeta.ResolveDelegate(type, path, callback); | |
} | |
public static void Add<T>(string path, T callback) where T : Delegate | |
{ | |
CSharpMeta.ResolveDelegate(path, callback); | |
} | |
private static void ResolveDelegate(object target, string path, Delegate callback) | |
{ | |
MemberHandler handler = CSharpMeta.ResolveMember(target, target.GetType(), path.Split('.'), 0); | |
if (handler is FieldHandler) | |
(handler as FieldHandler).Add(callback); | |
else if (handler is EventHandler) | |
(handler as EventHandler).Add(callback); | |
} | |
private static void ResolveDelegate(Type type, string path, Delegate callback) | |
{ | |
MemberHandler handler = CSharpMeta.ResolveMember(null, type, path.Split('.'), 0); | |
if (handler is FieldHandler) | |
(handler as FieldHandler).Add(callback); | |
else if (handler is EventHandler) | |
(handler as EventHandler).Add(callback); | |
} | |
private static void ResolveDelegate(string path, Delegate callback) | |
{ | |
MemberHandler handler = CSharpMeta.ResolveMember(path); | |
if (handler is FieldHandler) | |
(handler as FieldHandler).Add(callback); | |
else if (handler is EventHandler) | |
(handler as EventHandler).Add(callback); | |
} | |
public static EventHandler ResolveEvent(object target, string path) | |
{ | |
if (target != null) | |
return CSharpMeta.ResolveEvent(target, target.GetType(), path.Split('.'), 0); | |
return null; | |
} | |
public static EventHandler ResolveEvent<T>(string path) | |
{ | |
return CSharpMeta.ResolveEvent(null, typeof(T), path.Split('.'), 0); | |
} | |
public static EventHandler ResolveEvent(Type type, string path) | |
{ | |
if (type != null) | |
return CSharpMeta.ResolveEvent(null, type, path.Split('.'), 0); | |
return null; | |
} | |
public static EventHandler ResolveEvent(string path) | |
{ | |
Type type = CSharpMeta.ResolveType(path); | |
if (type != null) | |
return CSharpMeta.ResolveEvent(null, type, path.Split('.'), type.FullName.Split('.').Length); | |
return null; | |
} | |
public static FieldHandler ResolveField(object target, string path) | |
{ | |
if (target != null) | |
return CSharpMeta.ResolveField(target, target.GetType(), path.Split('.'), 0); | |
return null; | |
} | |
public static FieldHandler ResolveField<T>(string path) | |
{ | |
return CSharpMeta.ResolveField(null, typeof(T), path.Split('.'), 0); | |
} | |
public static FieldHandler ResolveField(Type type, string path) | |
{ | |
if (type != null) | |
return CSharpMeta.ResolveField(null, type, path.Split('.'), 0); | |
return null; | |
} | |
public static FieldHandler ResolveField(string path) | |
{ | |
Type type = CSharpMeta.ResolveType(path); | |
if (type != null) | |
return CSharpMeta.ResolveField(null, type, path.Split('.'), type.FullName.Split('.').Length); | |
return null; | |
} | |
private static void AddDelegate(object target, FieldInfo fieldInfo, Delegate callback) | |
{ | |
Type returnType; | |
Type[] arguments; | |
Delegate @delegate; | |
if (fieldInfo.FieldType.IsSubclassOf(typeof(Delegate))) | |
{ | |
MethodInfo method = fieldInfo.FieldType.GetMethod("Invoke"); | |
ParameterInfo[] parameters = method.GetParameters(); | |
int i = 0; | |
returnType = method.ReturnType; | |
if (callback.Method.IsStatic == false) | |
{ | |
arguments = new Type[parameters.Length + 1]; | |
arguments[i++] = callback.Target.GetType(); | |
} | |
else | |
arguments = new Type[parameters.Length]; | |
foreach (ParameterInfo param in parameters) | |
arguments[i++] = param.ParameterType; | |
if (CSharpMeta.Compare(method, callback.Method) == true) | |
{ | |
if (fieldInfo.FieldType.IsGenericType == false) | |
{ | |
callback = Delegate.CreateDelegate(fieldInfo.FieldType, callback.Target, callback.Method); | |
@delegate = (Delegate)fieldInfo.GetValue(target); | |
@delegate = Delegate.Combine(@delegate, callback); | |
fieldInfo.SetValue(target, @delegate); | |
} | |
else | |
{ | |
@delegate = (Delegate)fieldInfo.GetValue(target); | |
@delegate = Delegate.Combine(@delegate, callback); | |
fieldInfo.SetValue(target, @delegate); | |
} | |
return; | |
} | |
} | |
else | |
throw new Exception("Should never reach here"); | |
DynamicMethod m = new DynamicMethod("DynamicEvent", returnType, arguments, typeof(CSharpMeta)); | |
CSharpMeta.BuildDelegateBodyOpCodes(callback.Method, arguments, m); | |
@delegate = (Delegate)fieldInfo.GetValue(target); | |
Delegate newDelegate = m.CreateDelegate(fieldInfo.FieldType, callback.Target); | |
@delegate = Delegate.Combine(@delegate, newDelegate); | |
fieldInfo.SetValue(target, @delegate); | |
} | |
private static void AddEvent(object target, EventInfo eventInfo, Delegate callback) | |
{ | |
Type returnType; | |
Type[] arguments; | |
if (eventInfo.EventHandlerType.IsSubclassOf(typeof(Delegate))) | |
{ | |
MethodInfo method = eventInfo.EventHandlerType.GetMethod("Invoke"); | |
ParameterInfo[] parameters = method.GetParameters(); | |
int i = 0; | |
returnType = method.ReturnType; | |
if (callback.Method.IsStatic == false) | |
{ | |
arguments = new Type[parameters.Length + 1]; | |
arguments[i++] = callback.Target.GetType(); | |
} | |
else | |
arguments = new Type[parameters.Length]; | |
foreach (ParameterInfo param in parameters) | |
arguments[i++] = param.ParameterType; | |
if (CSharpMeta.Compare(method, callback.Method) == true) | |
{ | |
if (eventInfo.EventHandlerType.IsGenericType == false) | |
{ | |
callback = Delegate.CreateDelegate(eventInfo.EventHandlerType, callback.Target, callback.Method); | |
eventInfo.GetAddMethod(true).Invoke(target, new[] { callback }); | |
} | |
else | |
eventInfo.GetAddMethod(true).Invoke(target, new[] { callback }); | |
return; | |
} | |
} | |
else | |
throw new Exception("Should never reach here"); | |
DynamicMethod m = new DynamicMethod("DynamicEvent", returnType, arguments, typeof(CSharpMeta)); | |
CSharpMeta.BuildDelegateBodyOpCodes(callback.Method, arguments, m); | |
Delegate newDelegate = m.CreateDelegate(eventInfo.EventHandlerType, callback.Target); | |
eventInfo.GetAddMethod(true).Invoke(target, new[] { newDelegate }); | |
} | |
private static void BuildDelegateBodyOpCodes(MethodInfo callback, Type[] arguments, DynamicMethod m) | |
{ | |
ILGenerator cg = m.GetILGenerator(); | |
int argumentsLength = arguments.Length; | |
if (callback.IsStatic == false) | |
cg.Emit(OpCodes.Ldarg_0); // this | |
if (callback.GetParameters().Length > 0) | |
{ | |
// Allocate array for the variadic argument. | |
if (argumentsLength == 0) | |
cg.Emit(OpCodes.Ldc_I4_0); | |
else if (argumentsLength == 1) | |
cg.Emit(OpCodes.Ldc_I4_1); | |
else if (argumentsLength == 2) | |
cg.Emit(OpCodes.Ldc_I4_2); | |
else if (argumentsLength == 3) | |
cg.Emit(OpCodes.Ldc_I4_3); | |
else if (argumentsLength == 4) | |
cg.Emit(OpCodes.Ldc_I4_4); | |
else if (argumentsLength == 5) | |
cg.Emit(OpCodes.Ldc_I4_5); | |
else if (argumentsLength == 6) | |
cg.Emit(OpCodes.Ldc_I4_6); | |
else if (argumentsLength == 7) | |
cg.Emit(OpCodes.Ldc_I4_7); | |
else if (argumentsLength == 8) | |
cg.Emit(OpCodes.Ldc_I4_8); | |
else | |
cg.Emit(OpCodes.Ldc_I4, argumentsLength); | |
cg.Emit(OpCodes.Newarr, typeof(object)); | |
// Set the arguments into the array. | |
for (int i = 0, max = argumentsLength; i < max; ++i) | |
{ | |
cg.Emit(OpCodes.Dup); | |
cg.Emit(OpCodes.Ldc_I4, i); | |
cg.Emit(OpCodes.Ldarg, i); | |
cg.Emit(OpCodes.Stelem_Ref); | |
} | |
} | |
// Call the callback. | |
cg.Emit(OpCodes.Call, callback); | |
cg.Emit(OpCodes.Ret); | |
} | |
private static Type ResolveType(string path) | |
{ | |
string[] parts = path.Split('.'); | |
List<Assembly> assemblies = new List<Assembly>(AppDomain.CurrentDomain.GetAssemblies()); | |
for (int j = 0, max2 = assemblies.Count; j < max2; ++j) | |
{ | |
Assembly assembly = assemblies[j]; | |
Type[] types = assembly.GetTypes(); | |
for (int k = 0, max3 = types.Length; k < max3; ++k) | |
{ | |
Type type = types[k]; | |
string fullName = type.FullName; | |
if (fullName.StartsWith(parts[0]) == true && fullName[parts[0].Length] == '.') | |
{ | |
string[] typeParts = fullName.Split('.'); | |
int typePartsLength = typeParts.Length; | |
if (typePartsLength < parts.Length) | |
{ | |
int l = 1; | |
while (l < typePartsLength && typeParts[l] == parts[l]) | |
++l; | |
if (l == typePartsLength) | |
return type; | |
} | |
} | |
} | |
} | |
return null; | |
} | |
private static MemberHandler ResolveMember(string path) | |
{ | |
Type type = CSharpMeta.ResolveType(path); | |
if (type != null) | |
return CSharpMeta.ResolveMember(null, type, path.Split('.'), type.FullName.Split('.').Length); | |
return null; | |
} | |
private static MemberHandler ResolveMember(object target, Type type, string[] parts, int i) | |
{ | |
for (int max = parts.Length; i < max; ++i) | |
{ | |
MemberInfo[] members = type.GetMember(parts[i], MemberTypes.Field | MemberTypes.Property | (i + 1 == max ? MemberTypes.Event : 0), BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); | |
MemberInfo member = null; | |
if (members.Length == 0) | |
throw new Exception("Member not found at " + string.Join(".", parts, 0, i + 1) + " with " + type.FullName + "."); | |
else if (members.Length > 1) | |
{ | |
for (int j = 0, max2 = members.Length; j < max2; ++j) | |
{ | |
MemberInfo element = members[j]; | |
bool isCompiledGenerated = element.GetCustomAttribute<CompilerGeneratedAttribute>() != null; | |
if (member != null && isCompiledGenerated == false) | |
throw new Exception("Ambiguous members found at " + string.Join(".", parts, 0, i + 1) + " with " + type.FullName + "."); | |
if (isCompiledGenerated == false) | |
member = element; | |
} | |
} | |
else | |
member = members[0]; | |
if (i + 1 == max) | |
{ | |
if (member is FieldInfo) | |
return new FieldHandler(target, member as FieldInfo); | |
if (member is PropertyInfo) | |
return new PropertyHandler(target, member as PropertyInfo); | |
if (member is EventInfo) | |
return new EventHandler(target, member as EventInfo); | |
} | |
else | |
{ | |
if ((member.MemberType & MemberTypes.Field) != 0) | |
target = (member as FieldInfo).GetValue(target); | |
else if ((member.MemberType & MemberTypes.Property) != 0) | |
target = (member as PropertyInfo).GetValue(target); | |
else | |
throw new Exception("Invalid member at " + string.Join(".", parts, 0, i + 1) + " with " + type.FullName + "."); | |
} | |
if (target == null && i + 1 < max) | |
throw new Exception("Null member at " + string.Join(".", parts, 0, i + 1) + " with " + type.FullName + "."); | |
type = target.GetType(); | |
} | |
throw new Exception("Should not reach here."); | |
} | |
private static FieldHandler ResolveField(object target, Type type, string[] parts, int i) | |
{ | |
MemberHandler handler = CSharpMeta.ResolveMember(target, type, parts, i); | |
if (handler is FieldHandler) | |
return handler as FieldHandler; | |
throw new Exception($"Member at \"{string.Join(".", parts)}\" is not a field."); | |
} | |
private static EventHandler ResolveEvent(object target, Type type, string[] parts, int i) | |
{ | |
MemberHandler handler = CSharpMeta.ResolveMember(target, type, parts, i); | |
if (handler is EventHandler) | |
return handler as EventHandler; | |
throw new Exception($"Member at \"{string.Join(".", parts)}\" is not an event."); | |
} | |
private static T GetFieldOrPropertyValue<T>(Type type, string[] parts, int l) | |
{ | |
MemberInfo[] members = type.GetMember(parts[l], MemberTypes.Field | MemberTypes.Property, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); | |
if (members.Length == 0) | |
throw new Exception("Member not found at " + type.FullName + '.' + parts[l] + "."); | |
else if (members.Length > 1) | |
throw new Exception("Ambiguous members found at " + type.FullName + '.' + parts[l] + "."); | |
object origin; | |
if ((members[0].MemberType & MemberTypes.Field) != 0) | |
origin = (members[0] as FieldInfo).GetValue(null); | |
else if ((members[0].MemberType & MemberTypes.Property) != 0) | |
origin = (members[0] as PropertyInfo).GetValue(null); | |
else | |
throw new Exception("Member at " + type.FullName + '.' + parts[l] + " must be a field or a property ."); | |
return (T)CSharpMeta.GetFieldOrPropertyValue(origin, parts, l + 1); | |
} | |
private static object GetFieldOrPropertyValue(object origin, string[] parts, int i) | |
{ | |
if (origin == null) | |
return null; | |
Type type = origin.GetType(); | |
for (int max = parts.Length; i < max; ++i) | |
{ | |
MemberInfo[] members = type.GetMember(parts[i], MemberTypes.Field | MemberTypes.Property, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); | |
if (members.Length == 0) | |
throw new Exception("Member not found at " + string.Join(".", parts, 0, i + 1) + " with " + type.FullName + "."); | |
else if (members.Length > 1) | |
throw new Exception("Ambiguous members found at " + string.Join(".", parts, 0, i + 1) + " with " + type.FullName + "."); | |
MemberInfo member = members[0]; | |
if ((member.MemberType & MemberTypes.Field) != 0) | |
{ | |
origin = (member as FieldInfo).GetValue(origin); | |
type = (member as FieldInfo).FieldType; | |
} | |
else if ((member.MemberType & MemberTypes.Property) != 0) | |
{ | |
origin = (member as PropertyInfo).GetValue(origin); | |
type = (member as PropertyInfo).PropertyType; | |
} | |
else | |
throw new Exception("Invalid member at " + string.Join(".", parts, 0, i + 1) + " with " + type.FullName + "."); | |
if (origin == null && i + 1 < max) | |
throw new Exception("Null member at " + string.Join(".", parts, 0, i + 1) + " with " + type.FullName + "."); | |
} | |
return origin; | |
} | |
private static bool Compare(MethodInfo a, MethodInfo b) | |
{ | |
if (a.ReturnType != b.ReturnType) | |
return false; | |
ParameterInfo[] aParameters = a.GetParameters(); | |
ParameterInfo[] bParameters = b.GetParameters(); | |
if (aParameters.Length != bParameters.Length) | |
return false; | |
int i = 0; | |
while (i < aParameters.Length) | |
{ | |
if (aParameters[i].ParameterType != bParameters[i].ParameterType) | |
return false; | |
++i; | |
} | |
return true; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void TestVarArgs(params object[] args) | |
{ | |
Debug.Log("TestVarArgs(" + args.Length + ")"); | |
} | |
void Test() | |
{ | |
// Register on an event. | |
CSharpMeta.Add("UnityEditor.ShortcutManagement.ShortcutIntegration.instance.profileManager.shortcutBindingChanged", TestVarArgs); | |
// Get the value to an internal member. | |
CSharpMeta.Get<Rect>(Resources.FindObjectsOfTypeAll<EditorWindow>()[0], "rootVisualContainer.worldClip"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment