Skip to content

Instantly share code, notes, and snippets.

@Mikilo
Last active January 27, 2022 03:32
Show Gist options
  • Save Mikilo/8edbf562d7d3d39867bba1c4687240e5 to your computer and use it in GitHub Desktop.
Save Mikilo/8edbf562d7d3d39867bba1c4687240e5 to your computer and use it in GitHub Desktop.
CSharpMeta
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;
}
}
}
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