In this response, I came up with the following helper method that could be reused by different Task creators to convert events into task completion sources.
// Helper method
static Task<T> TaskFromEventHelper<T>(object target, string eventName, Func<TaskCompletionSource<T>, object> resultSetterFactory) {
var tcs = new TaskCompletionSource<T>();
var addMethod = target.GetType().GetEvent(eventName).GetAddMethod();
var delegateType = addMethod.GetParameters()[0].ParameterType;
var d = Delegate.CreateDelegate(delegateType, resultSetterFactory(tcs), "Invoke");
addMethod.Invoke(target, new object[] {d});
return tcs.Task;
}
// Empty events (Action style)
static Task TaskFromEvent(object target, string eventName) {
return TaskFromEventHelper(target, eventName, (Func<TaskCompletionSource<object>, object>)(tcs => (Action)(() => tcs.SetResult(null))));
}
// One-value events (Action<T> style)
static Task<T> TaskFromEvent<T>(object target, string eventName) {
return TaskFromEventHelper(target, eventName, (Func<TaskCompletionSource<T>, object>)(tcs => (Action<T>)(tcs.SetResult)));
}
// Two-value events (Action<T1, T2> or EventHandler style)
static Task<Tuple<T1, T2>> TaskFromEvent<T1, T2>(object target, string eventName) {
return TaskFromEventHelper(target, eventName, (Func<TaskCompletionSource<Tuple<T1, T2>>, object>)(tcs => (Action<T1, T2>)((t1, t2) => tcs.SetResult(Tuple.Create(t1, t2)))));
}
In each of the three examples I gave that use the helper method, there's a tcs.SetResult component, which makes me think there's a way to move that to the helper method too, which might perhaps simplify the signatures, so that perhaps the helper method would just have to accept a Func<?, T>, where that Func would take the output of the event and convert it to whatever tcs.SetResult takes.
i.e., I'm thinking there must be a way to create a helper so I can write it as
// Empty events (Action style)
static Task TaskFromEvent(object target, string eventName) {
return TaskFromEventHelper<object>(target, eventName, new Func<object>(() => null));
}
// One-value events (Action<T> style)
static Task<T> TaskFromEvent<T>(object target, string eventName) {
return TaskFromEventHelper<T>(target, eventName, new Func<T, T>(t => t));
}
// Two-value events (Action<T1, T2> or EventHandler style)
static Task<Tuple<T1, T2>> TaskFromEvent<T1, T2>(object target, string eventName) {
return TaskFromEventHelper<Tuple<T1, T2>>(target, eventName, new Func<T1, T2, Tuple<T1, T2>>(Tuple.Create));
}
, but that's why I don't know the ? in Func<?, T> above. This one for example needs ? to be two parameters. Could it be passed in as object somehow? I have a feeling it could be possible, but if so it needs some real reflection magic.
You can use Expression:
static Task<T> TaskFromEventHelper<T>(object target, string eventName, Delegate resultSetter)
{
var tcs = new TaskCompletionSource<T>();
var addMethod = target.GetType().GetEvent(eventName).GetAddMethod();
var delegateType = addMethod.GetParameters()[0].ParameterType;
var methodInfo = delegateType.GetMethod("Invoke");
var parameters = methodInfo.GetParameters()
.Select(a => Expression.Parameter(a.ParameterType))
.ToArray();
// building method, which argument count and
// their types are not known at compile time
var exp = // (%arguments%) => tcs.SetResult(resultSetter.Invoke(%arguments%))
Expression.Lambda(
delegateType,
Expression.Call(
Expression.Constant(tcs),
tcs.GetType().GetMethod("SetResult"),
Expression.Call(
Expression.Constant(resultSetter),
resultSetter.GetType().GetMethod("Invoke"),
parameters)),
parameters);
addMethod.Invoke(target, new object[] { exp.Compile() });
return tcs.Task;
}
Related
I try to implement a decorator pattern for handling error in database transactions. I have no problem with standard Func and Actions, but i have difficulties with functions having out parameter.
Here many topics with same question, and i figured out to implement my own delegate:
public delegate TResult FuncWithOut<T1, T2, TResult>(T1 arg1, out T2 arg2);
1) But i don't found how to implement method based on this delegate:
private void SafetyExecuteMethod(Action action)
{
try
{
action();
}
catch (Exception ex)
{
// Some handling
}
}
private T SafetyExecuteFunction<T>(Func<T> func)
{
T result = default(T);
SafetyExecuteMethod(() => result = func.Invoke());
return result;
}
private SafetyExecuteFunctionWithOut // ??
{
// ??
}
2) And how to call this method:
public bool UserExists(string name)
{
return SafetyExecuteFunction(() => _innerSession.UserExists(name));
}
public void CreateUser(string name, string password)
{
SafetyExecuteMethod(() => _innerSession.CreateUser(name, password));
}
public bool CanUpdateUser(string userName, out string errorMessage)
{
// ??
// _innerSession.CanUpdateUser(userName, out errorMessage);
}
Just use the same scheme as in your example of SafetyExecuteFunction<T>(Func<T> func).
One thing you have to pay attention to is that you need to use a temporary local variable for the out parameter.
private TResult SafetyExecuteFunctionWithOut<T1, T2, TResult>(FuncWithOut<T1, T2, TResult> func, T1 arg1, out T2 arg2)
{
TResult result = default(TResult);
T2 arg2Result = default(T2); // Need to use a temporary local variable here
SafetyExecuteMethod(() => result = func(arg1, out arg2Result));
arg2 = arg2Result; // And then assign it to the actual parameter after calling the delegate.
return result;
}
Calling the function does then work like this:
public bool CanUpdateUser(string userName, out string errorMessage)
{
bool result = SafetyExecuteFunctionWithOut<string, string, bool>(_innerSession.CanUpdateUser, userName, out errorMessage);
return result;
}
Note, that you have to pass _innerSession.CanUpdateUser as a parameter to SafetyExecuteFunctionWithOut instead of using a lambda expression.
Using the naive attempt:
private TResult SafetyExecuteFunctionWithOut<T1, T2, TResult>(FuncWithOut<T1, T2, TResult> func, T1 arg1, out T2 arg2)
{
TResult result = default(TResult);
SafetyExecuteMethod(() => result = func(arg1, out arg2));
return result;
}
creates the error message:
CS1628 Cannot use ref or out parameter 'arg2' inside an anonymous
method, lambda expression, or query expression
Why you are not allowed to do that is explained in this answer.
Using the new async/await model it's fairly straightforward to generate a Task that is completed when an event fires; you just need to follow this pattern:
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;
}
This then allows:
await FromEvent(new MyClass());
The problem is that you need to create a new FromEvent method for every event in every class that you would like to await on. That could get really large really quick, and it's mostly just boilerplate code anyway.
Ideally I would like to be able to do something like this:
await FromEvent(new MyClass().OnCompletion);
Then I could re-use the same FromEvent method for any event on any instance. I've spent some time trying to create such a method, and there are a number of snags. For the code above it will generate the following error:
The event 'Namespace.MyClass.OnCompletion' can only appear on the left hand side of += or -=
As far as I can tell, there won't ever be a way of passing the event like this through code.
So, the next best thing seemed to be trying to pass the event name as a string:
await FromEvent(new MyClass(), "OnCompletion");
It's not as ideal; you don't get intellisense and would get a runtime error if the event doesn't exist for that type, but it could still be more useful than tons of FromEvent methods.
So it's easy enough to use reflection and GetEvent(eventName) to get the EventInfo object. The next problem is that the delegate of that event isn't known (and needs to be able to vary) at runtime. That makes adding an event handler hard, because we need to dynamically create a method at runtime, matching a given signature (but ignoring all parameters) that accesses a TaskCompletionSource that we already have and sets its result.
Fortunately I found this link which contains instructions on how to do [almost] exactly that via Reflection.Emit. Now the problem is that we need to emit IL, and I have no idea how to access the tcs instance that I have.
Below is the progress that I've made towards finishing this:
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;
}
What IL could I possibly emit that would allow me to set the result of the TaskCompletionSource? Or, alternatively, is there another approach to creating a method that returns a Task for any arbitrary event from an arbitrary type?
Here you go:
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;
}
}
This code will work for almost all events that return void (regardless of the parameter list).
It can be improved to support any return values if necessary.
You can see the difference between Dax's and mine methods below:
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;
}
Briefly, my code supports really any kind of delegate type. You shouldn't (and don't need to) specify it explicitly like TaskFromEvent<int, string>.
This will give you what you need without needing to do any ilgen, and way simpler. It works with any kind of event delegates; you just have to create a different handler for each number of parameters in your event delegate. Below are the handlers you'd need for 0..2, which should be the vast majority of your use cases. Extending to 3 and above is a simple copy and paste from the 2-parameter method.
This is also more powerful than the ilgen method because you can use any values created by the event in your async pattern.
// Empty events (Action style)
static Task TaskFromEvent(object target, string eventName) {
var addMethod = target.GetType().GetEvent(eventName).GetAddMethod();
var delegateType = addMethod.GetParameters()[0].ParameterType;
var tcs = new TaskCompletionSource<object>();
var resultSetter = (Action)(() => tcs.SetResult(null));
var d = Delegate.CreateDelegate(delegateType, resultSetter, "Invoke");
addMethod.Invoke(target, new object[] { d });
return tcs.Task;
}
// One-value events (Action<T> style)
static Task<T> TaskFromEvent<T>(object target, string eventName) {
var addMethod = target.GetType().GetEvent(eventName).GetAddMethod();
var delegateType = addMethod.GetParameters()[0].ParameterType;
var tcs = new TaskCompletionSource<T>();
var resultSetter = (Action<T>)tcs.SetResult;
var d = Delegate.CreateDelegate(delegateType, resultSetter, "Invoke");
addMethod.Invoke(target, new object[] { d });
return tcs.Task;
}
// Two-value events (Action<T1, T2> or EventHandler style)
static Task<Tuple<T1, T2>> TaskFromEvent<T1, T2>(object target, string eventName) {
var addMethod = target.GetType().GetEvent(eventName).GetAddMethod();
var delegateType = addMethod.GetParameters()[0].ParameterType;
var tcs = new TaskCompletionSource<Tuple<T1, T2>>();
var resultSetter = (Action<T1, T2>)((t1, t2) => tcs.SetResult(Tuple.Create(t1, t2)));
var d = Delegate.CreateDelegate(delegateType, resultSetter, "Invoke");
addMethod.Invoke(target, new object[] { d });
return tcs.Task;
}
Use would be like this. As you can see, even though the event is defined in a custom delegate, it still works. And you can capture the evented values as a tuple.
static async void Run() {
var result = await TaskFromEvent<int, string>(new MyClass(), "Fired");
Console.WriteLine(result); // (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;
}
Here's a helper function that'll allow you to write the TaskFromEvent functions in just one line each, if the above three methods are too much copy-and-paste for your preferences. Credit has to be given to max for simplifying what I had originally.
If you're willing to have one method per delegate type, you can do something like:
Task FromEvent(Action<Action> add)
{
var tcs = new TaskCompletionSource<bool>();
add(() => tcs.SetResult(true));
return tcs.Task;
}
You would use it like:
await FromEvent(x => new MyClass().OnCompletion += x);
Be aware that this way you never unsubscribe from the event, that may or may not be a problem for you.
If you're using generic delegates, one method per each generic type is enough, you don't need one for each concrete type:
Task<T> FromEvent<T>(Action<Action<T>> add)
{
var tcs = new TaskCompletionSource<T>();
add(x => tcs.SetResult(x));
return tcs.Task;
}
Although type inference doesn't work with that, you have to explicitly specify the type parameter (assuming the type of OnCompletion is Action<string> here):
string s = await FromEvent<string>(x => c.OnCompletion += x);
I faced this problem by trying to write GetAwaiter extension method for System.Action, forgetting that System.Action is immutable and by passing it as an argument you make a copy. However, you do not make a copy if you pass it with ref keyword, thus:
public static class AwaitExtensions
{
public static Task FromEvent(ref Action action)
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
action += () => tcs.SetResult(null);
return tcs.Task;
}
}
Usage:
await AwaitExtensions.FromEvent(ref OnActionFinished);
Note: TCS listener remain subscribed
I'm trying to compile expression with void return type to delegate with object return type. I would like to get null value in that case.
var inputArg0 = Expression.Parameter(typeof(object), "arg0");
var inputArg1 = Expression.Parameter(typeof(CancellationToken), "arg1");
var inputArg0Converted = Expression.Convert(inputArg0, inputArg0Type);
var instanceExpression = Expression.Constant(instance);
var call = Expression.Call(instanceExpression, methodInfo, inputArg0Converted, inputArg1);
var lambda = Expression.Lambda<Func<object, CancellationToken, object>>(call, inputArg0, inputArg1);
var _delegate = lambda.Compile();
// works
public string Ping(object e, CancellationToken cancellationToken)
{
return "OK";
}
// can't compile this MethodInfo to Func<object, CancellationToken, object>
public void Update(ObjectChangedEvent e, CancellationToken cancellationToken)
{
// do stuff
}
You can use Actions instead of Funcs for void return types, try this:
Action<object, CancellationToken, object>
You can wrap your action method inside of a Func that returns null, like this:
Func<object, CancellationToken, object> l = (object e, CancellationToken d ) =>
{
Update((ObjectChangedEvent) e, d);
return null;
};
I want to be able to subscribe to any event of any object by passing event's name and Action dependent on client code. I have following code
public static class EventSubscriber
{
public static object Subscriber<TEventArgs>(
this object obj,
string eventName,
Action handler,
Func<TEventArgs, bool> canExecute)
{
var eventInfo = obj.GetType().
GetEvent(eventName);
if (eventInfo == null)
throw new ArgumentException("Event name provided does not exist", nameof(eventName));
var handlerArgs = eventInfo.EventHandlerType.
GetMethod("Invoke").
GetParameters()
.Select(p => p.ParameterType).ToArray();
var method = new DynamicMethod("method", typeof (void), handlerArgs);
var generator = method.GetILGenerator(256);
generator.EmitCall(OpCodes.Call, handler.Method, null);
eventInfo.
AddEventHandler(
obj,
method.CreateDelegate(eventInfo.EventHandlerType));
return obj;
}
}
Usage of code above :
var Polygons = new ObservableCollection<Polygon>(myList);
Polygons.Subscriber<NotifyCollectionChangedEventArgs>
("CollectionChanged",
() => MessageBox.Show("hello"),
e => e.OldItems != null);
It causes an InvalidProgramException when the event fires.
I know this is a tricky one and i could simply subscribe using += but could anybody tell why my code crashes?
I suppose something wrong with ILGenerator.Emit, any suggestions?
You forgot to return at the end of the DynamicMethod.
var method = new DynamicMethod("method", typeof (void), handlerArgs);
var generator = method.GetILGenerator(256);
generator.EmitCall(OpCodes.Call, handler.Method, null);
generator.Emit(OpCodes.Ret); //every method must have a return statement
And the class that the compiler creates for the () => MessageBox.Show("hello") lambda is private.[reference]
When you use a public static method in a public class instead it works.
var Polygons = new ObservableCollection<Polygon>(myList);
Polygons.Subscriber<NotifyCollectionChangedEventArgs>
("CollectionChanged",
() => MessageBox.Show("hello"), //must in a public class and a public static method
e => e.OldItems != null);
I am trying to call a method that takes in a function, but using relection. I've gotten it to work when the method is parameterless but I can't figure out how to invoke it when there are arguments. This is a contrived simplified example, but this boils down the problem. I will not know the arguments to Add until runtime.
Any pointers? Do I have to use expression trees? Is there an easier way to do this?
public void Main()
{
//works
CallFunction(typeof (Processor), "Count");
//I don't understand what I need to modify to make add work
CallFunction(typeof (Processor), "Add");
}
public void CallFunction(Type type, string methodToCall)
{
var targetMethod = type.GetMethod(methodToCall);
var constructedType = typeof (MethodCaller<>).MakeGenericType(type);
dynamic target = Activator.CreateInstance(constructedType);
var method = constructedType.GetMethod("Do").MakeGenericMethod(targetMethod.ReturnType);
var func = typeof (Func<,>).MakeGenericType(type, targetMethod.ReturnType);
var toCall = Delegate.CreateDelegate(func, targetMethod);
method.Invoke(target, new object[] { toCall });
}
public class Processor
{
public int Count()
{
return 1;
}
public int Add(int toAdd)
{
return 1 + toAdd;
}
}
public class MethodCaller<TParm> where TParm : new()
{
public TResult Do<TResult>(Func<TParm, TResult> func)
{
return func(new TParm());
}
}
Like type.InvokeMember(method_name, System.Reflection.BindingFlags.InvokeMethod, null, type_instance, new object[] { param1, param2, param3 }); ?
https://msdn.microsoft.com/en-us/library/66btctbe(v=vs.110).aspx
Actually whole CallFunction method can be simplified like this:
public void CallFunction(Type type, string methodToCall, params object[] args)
{
// Why this part is so complex? Do I miss something?
//var constructedType = typeof (MethodCaller<>).MakeGenericType(type);
//dynamic target = Activator.CreateInstance(constructedType);
var target = Activator.CreateInstance(type);
var result = type.InvokeMember(method_name, System.Reflection.BindingFlags.InvokeMethod, null, target, args);
// ... do something with result if you need ...
}
If you need MethodCaller.Do, but can sacrifice types on signature (or some performance on boxing/unboxing for DoTyped)
public void CallFunction(Type type, string methodToCall, params object[] args)
{
var delegate_wrapper = new Func<object, object>(
instance => type.InvokeMember(methodToCall, BindingFlags.InvokeMethod, null, instance, args)
);
var target_method = type.GetMethod(methodToCall);
var mc_custom_type = typeof (MethodCaller<>).MakeGenericType(type);
var mc_instance = Activator.CreateInstance(mc_custom_type);
var mc_custom_method = mc_custom_type.GetMethod("Do").MakeGenericMethod(target_method.ReturnType);
mc_custom_method.Invoke(mc_instance, new object[] { delegate_wrapper });
}
public class MethodCaller<TParm> where TParm : new()
{
public TResult DoTyped<TResult>(Func<TParm, TResult> func)
{
return Do<TResult>(oinstance=>func((TParm)oinstance));
}
public TResult Do<TResult>(Func<object, object> func)
{
Console.WriteLine("I AM DO");
return (TResult)func(new TParm());
}
}
Have you tried looking at Impromptu framework. It is primarily for duck typing but the library provide rather slick api to access methods and pass arguments.
https://github.com/ekonbenefits/impromptu-interface/wiki/UsageReallyLateBinding