我阅读了所有的答案,真的想避免使用"TryGetMember
"和"TrySetMember
"的解决方案,因为它感觉非常不自然。
(您可以将事件保存在变量中,或编写类似于var x = dynObj.MyEvent + MyAction;
的内容,而不会出现编译时或运行时错误。)
在查看dahlbyk的答案时,我发现在反编译代码中有一个检查"IsEvent
",我希望它只返回true
。
我会尽量简短 - 我用一种非常非常非常hackish的方式使其工作(超过了“常规”反射hackish级别!)
对于此检查,没有调用DynamicObject
API中的任何方法,除了GetMetaObject
。我尝试重写它,但是我无法获得任何积极的结果。也许有更聪明的人可以证明我做得过头了。
在没有其他选择的情况下,我采用了最糟糕的方法来解决这个问题:颠覆.NET的内部工作,使用运行时补丁程序和大量反射。
解决方案是劫持类型为
Microsoft.CSharp.RuntimeBinder.RuntimeBinder
的程序集
Microsoft.CSharp
中名为
BindIsEvent
的函数的返回值。
当提供我的特定目标(
SomeWrapperClass
)和事件名称(
"OnMyEvent"
)时,它将其结果从"false"替换为"true"。
(实际上它并不返回一个
bool
,而是返回一个丑陋的内部类
EXPR
,大多数反射代码都试图创建它)。
在此处提供的Microsoft提供的参考源代码
我的解决方案使用了很多反射,所以我编写了一些辅助函数(Steal
等...)
对于这个解决方案,你需要从NuGet获取Harmony补丁库。
using System;
using System.Linq;
using System.Reflection;
using static DynamicEventsTest.ReflectionTricks;
namespace DynamicEventsTest
{
public static class ReflectionTricks
{
private static BindingFlags ALL = (BindingFlags)0xffff;
public static T Steal<T>(Type t, object o, string member) =>
(T)(t.GetFields(ALL).SingleOrDefault(fld => fld.Name == member)?.GetValue(o) ??
t.GetProperties(ALL).SingleOrDefault(prop => prop.Name == member)?.GetValue(o) ??
(t.GetMethods(ALL).Where(mth => mth.Name == member).Skip(1).Any() ?
(object)(t.GetMethods(ALL).Where(mth => mth.Name == member).ToArray()) :
t.GetMethods(ALL).SingleOrDefault(mth => mth.Name == member)));
public static T Steal<T>(object o, string member) => Steal<T>(o.GetType(), o, member);
public static T Steal<T>(Type o, string member) => Steal<T>(o, null, member);
public static T DeepSteal<T>(object o, string pathToInnerMember)
{
if (pathToInnerMember.Contains("."))
{
string rest = pathToInnerMember.Substring(0, pathToInnerMember.LastIndexOf('.'));
pathToInnerMember = pathToInnerMember.Substring(pathToInnerMember.LastIndexOf('.') + 1);
o = DeepSteal<object>(o, rest);
}
return Steal<T>(o, pathToInnerMember);
}
public static MethodInfo Overload(this MethodInfo[] overloads, params Type[] types) =>
overloads.SingleOrDefault(
mi => mi.GetParameters().Length == types.Length &&
mi.GetParameters().Zip(types, (pi, expectedType) => pi.ParameterType.IsAssignableFrom(expectedType)).All(b => b));
}
internal class Program
{
private static void PostHook(object __instance, object __0, object __1, object __2, ref object __result)
{
string name = Steal<string>(__0, "Name");
Console.WriteLine("Trying to access: " + name);
Array array = (Array)__1;
object wrappedTarget = array.GetValue(0);
object target = Steal<object>(wrappedTarget, "Value");
if (target is SomeWrapperClass && name == "OnMyEvent")
{
Type[] types = __instance.GetType().Assembly.GetTypes();
bool THE_ACTUAL_BOOLEAN_RESULT = true;
Type PredefinedType = types.Single(t => t.Name.EndsWith("PredefinedType"));
var PT_BOOL = Steal<object>(PredefinedType, "PT_BOOL");
Type ConstValFactoryType = types.SingleOrDefault(t => t.Name.EndsWith("ConstValFactory"));
MethodInfo GetBool = Steal<MethodInfo>(ConstValFactoryType, "GetBool");
var CONSTVAL = GetBool.Invoke(null, new object[1] { THE_ACTUAL_BOOLEAN_RESULT });
object SymbolLoader = Steal<object>(__instance, "SymbolLoader");
var boolType = Steal<MethodInfo[]>(SymbolLoader, "GetReqPredefType").Overload(PredefinedType)
.Invoke(SymbolLoader, new object[1] { PT_BOOL });
object m_exprFactory = Steal<object>(__instance, "m_exprFactory");
MethodInfo CreateConstant = Steal<MethodInfo[]>(m_exprFactory, "CreateConstant").Overload(boolType.GetType(), CONSTVAL.GetType());
var replacementResults = CreateConstant.Invoke(m_exprFactory, new object[2] { boolType, CONSTVAL });
Console.WriteLine(" >> Original Results: " + __result);
bool bRes = DeepSteal<bool>(__result, "val.boolVal");
Console.WriteLine(" >> Converted to bool: " + bRes);
Console.WriteLine(" >> Replacement Results: " + replacementResults);
bRes = DeepSteal<bool>(replacementResults, "val.boolVal");
Console.WriteLine(" >> Converted to bool: " + bRes);
__result = replacementResults;
}
}
static void Main(string[] args)
{
AppDomain d = AppDomain.CurrentDomain;
Assembly microsoftCSharpAssembly = d.GetAssemblies().Where(x => x.FullName.Contains("Microsoft.CSharp")).Single();
Type[] types = microsoftCSharpAssembly.GetTypes();
var RuntimeBinder = types.Single(yy => yy.Name.EndsWith("RuntimeBinder"));
MethodInfo method = Steal<MethodInfo>(RuntimeBinder, "BindIsEvent");
HarmonyLib.Harmony harmony = new HarmonyLib.Harmony("some.string");
MethodInfo postHookMethod = typeof(Program).GetMethod("PostHook", BindingFlags.Static | BindingFlags.NonPublic);
harmony.Patch(method, postfix: new HarmonyLib.HarmonyMethod(postHookMethod));
dynamic obj = new SomeWrapperClass();
obj.OnMyEvent += (Action)MyEventHandler;
}
public static void MyEventHandler()
{
}
}
}
已在.NET框架4.8上测试,但实际效果因版本差异而异(微软可以在任何.NET版本中更改这些内部API)。