gpt4 book ai didi

c# - 通用 FromEvent 方法

转载 作者:太空狗 更新时间:2023-10-29 19:49:37 29 4
gpt4 key购买 nike

使用新的 async/await 模型,生成一个在事件触发时完成的 Task 相当简单;你只需要遵循这个模式:

public class MyClass
{
public event Action OnCompletion;
}

public static Task FromEvent(MyClass obj)
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();

obj.OnCompletion += () =>
{
tcs.SetResult(null);
};

return tcs.Task;
}

然后这允许:

await FromEvent(new MyClass());

问题是您需要为您希望等待 的每个类中的每个事件创建一个新的FromEvent 方法。这可能会很快变得非常大,而且它主要只是样板代码。

理想情况下,我希望能够做这样的事情:

await FromEvent(new MyClass().OnCompletion);

然后我可以为任何实例上的任何事件重复使用相同的 FromEvent 方法。我花了一些时间尝试创建这样的方法,但遇到了很多问题。对于上面的代码,它将产生以下错误:

The event 'Namespace.MyClass.OnCompletion' can only appear on the left hand side of += or -=

据我所知,永远没有办法通过代码传递这样的事件。

因此,下一个最好的事情似乎是尝试将事件名称作为字符串传递:

await FromEvent(new MyClass(), "OnCompletion");

不太理想;如果该类型的事件不存在,您不会获得智能感知并且会收到运行时错误,但它仍然比大量的 FromEvent 方法更有用。

因此很容易使用反射和 GetEvent(eventName) 来获取 EventInfo 对象。下一个问题是该事件的委托(delegate)在运行时是未知的(并且需要能够改变)。这使得添加事件处理程序变得困难,因为我们需要在运行时动态创建一个方法,匹配给定的签名(但忽略所有参数)访问我们已经拥有的 TaskCompletionSource 并设置其结果。

幸运的是我找到了this link其中包含有关如何通过 Reflection.Emit [几乎] 完全做到这一点的说明。现在的问题是我们需要发出 IL,而我不知道如何访问我拥有的 tcs 实例。

以下是我完成这项工作所取得的进展:

public static Task FromEvent<T>(this T obj, string eventName)
{
var tcs = new TaskCompletionSource<object>();
var eventInfo = obj.GetType().GetEvent(eventName);

Type eventDelegate = eventInfo.EventHandlerType;

Type[] parameterTypes = GetDelegateParameterTypes(eventDelegate);
DynamicMethod handler = new DynamicMethod("unnamed", null, parameterTypes);

ILGenerator ilgen = handler.GetILGenerator();

//TODO ilgen.Emit calls go here

Delegate dEmitted = handler.CreateDelegate(eventDelegate);

eventInfo.AddEventHandler(obj, dEmitted);

return tcs.Task;
}

我可以发出什么 IL 来允许我设置 TaskCompletionSource 的结果?或者,是否有另一种方法来创建一个方法,该方法为任意类型的任意事件返回任务?

最佳答案

给你:

internal class TaskCompletionSourceHolder
{
private readonly TaskCompletionSource<object[]> m_tcs;

internal object Target { get; set; }
internal EventInfo EventInfo { get; set; }
internal Delegate Delegate { get; set; }

internal TaskCompletionSourceHolder(TaskCompletionSource<object[]> tsc)
{
m_tcs = tsc;
}

private void SetResult(params object[] args)
{
// this method will be called from emitted IL
// so we can set result here, unsubscribe from the event
// or do whatever we want.

// object[] args will contain arguments
// passed to the event handler
m_tcs.SetResult(args);
EventInfo.RemoveEventHandler(Target, Delegate);
}
}

public static class ExtensionMethods
{
private static Dictionary<Type, DynamicMethod> s_emittedHandlers =
new Dictionary<Type, DynamicMethod>();

private static void GetDelegateParameterAndReturnTypes(Type delegateType,
out List<Type> parameterTypes, out Type returnType)
{
if (delegateType.BaseType != typeof(MulticastDelegate))
throw new ArgumentException("delegateType is not a delegate");

MethodInfo invoke = delegateType.GetMethod("Invoke");
if (invoke == null)
throw new ArgumentException("delegateType is not a delegate.");

ParameterInfo[] parameters = invoke.GetParameters();
parameterTypes = new List<Type>(parameters.Length);
for (int i = 0; i < parameters.Length; i++)
parameterTypes.Add(parameters[i].ParameterType);

returnType = invoke.ReturnType;
}

public static Task<object[]> FromEvent<T>(this T obj, string eventName)
{
var tcs = new TaskCompletionSource<object[]>();
var tcsh = new TaskCompletionSourceHolder(tcs);

EventInfo eventInfo = obj.GetType().GetEvent(eventName);
Type eventDelegateType = eventInfo.EventHandlerType;

DynamicMethod handler;
if (!s_emittedHandlers.TryGetValue(eventDelegateType, out handler))
{
Type returnType;
List<Type> parameterTypes;
GetDelegateParameterAndReturnTypes(eventDelegateType,
out parameterTypes, out returnType);

if (returnType != typeof(void))
throw new NotSupportedException();

Type tcshType = tcsh.GetType();
MethodInfo setResultMethodInfo = tcshType.GetMethod(
"SetResult", BindingFlags.NonPublic | BindingFlags.Instance);

// I'm going to create an instance-like method
// so, first argument must an instance itself
// i.e. TaskCompletionSourceHolder *this*
parameterTypes.Insert(0, tcshType);
Type[] parameterTypesAr = parameterTypes.ToArray();

handler = new DynamicMethod("unnamed",
returnType, parameterTypesAr, tcshType);

ILGenerator ilgen = handler.GetILGenerator();

// declare local variable of type object[]
LocalBuilder arr = ilgen.DeclareLocal(typeof(object[]));
// push array's size onto the stack
ilgen.Emit(OpCodes.Ldc_I4, parameterTypesAr.Length - 1);
// create an object array of the given size
ilgen.Emit(OpCodes.Newarr, typeof(object));
// and store it in the local variable
ilgen.Emit(OpCodes.Stloc, arr);

// iterate thru all arguments except the zero one (i.e. *this*)
// and store them to the array
for (int i = 1; i < parameterTypesAr.Length; i++)
{
// push the array onto the stack
ilgen.Emit(OpCodes.Ldloc, arr);
// push the argument's index onto the stack
ilgen.Emit(OpCodes.Ldc_I4, i - 1);
// push the argument onto the stack
ilgen.Emit(OpCodes.Ldarg, i);

// check if it is of a value type
// and perform boxing if necessary
if (parameterTypesAr[i].IsValueType)
ilgen.Emit(OpCodes.Box, parameterTypesAr[i]);

// store the value to the argument's array
ilgen.Emit(OpCodes.Stelem, typeof(object));
}

// load zero-argument (i.e. *this*) onto the stack
ilgen.Emit(OpCodes.Ldarg_0);
// load the array onto the stack
ilgen.Emit(OpCodes.Ldloc, arr);
// call this.SetResult(arr);
ilgen.Emit(OpCodes.Call, setResultMethodInfo);
// and return
ilgen.Emit(OpCodes.Ret);

s_emittedHandlers.Add(eventDelegateType, handler);
}

Delegate dEmitted = handler.CreateDelegate(eventDelegateType, tcsh);
tcsh.Target = obj;
tcsh.EventInfo = eventInfo;
tcsh.Delegate = dEmitted;

eventInfo.AddEventHandler(obj, dEmitted);
return tcs.Task;
}
}

此代码适用于几乎所有返回 void 的事件(无论参数列表如何)。

如有必要,可以改进以支持任何返回值。

您可以在下面看到 Dax 的方法和我的方法之间的区别:

static async void Run() {
object[] result = await new MyClass().FromEvent("Fired");
Console.WriteLine(string.Join(", ", result.Select(arg =>
arg.ToString()).ToArray())); // 123, abcd
}

public class MyClass {
public delegate void TwoThings(int x, string y);

public MyClass() {
new Thread(() => {
Thread.Sleep(1000);
Fired(123, "abcd");
}).Start();
}

public event TwoThings Fired;
}

简单地说,我的代码真正支持任何类型的委托(delegate)类型。您不应该(也不需要)像 TaskFromEvent<int, string> 那样明确指定它.

关于c# - 通用 FromEvent 方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12865848/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com