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);
Related
I have a type variable
using System;
using System.Linq;
using System.Reflection;
...
var validateFuncType = typeof(Func<,>).MakeGenericType(someVariableType, typeof(bool));
Now I check if someVariableType follows a convention,
var validateOfType = someVariableType
.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.SingleOrDefault(mi =>
{
if (mi.Name != "Validate" || mi.ReturnType != typeof(bool))
{
return false;
}
var parameters = mi.GetParameters();
return parameters.Length == 0;
});
then depending on the check
object validateFunc;
if (validateOfType == null)
{
validateFunc = // some noop func that returns true.
// e.g. _ => true;
}
else
{
validateFunc = // a delegate that calls the conventional validate
// e.g. someVariable => someVariable.Validate();
}
instantiate an instance of the delegate type.
Can you help me do that, how can I instantiate validateFuncType, that calls the conventional implementation, if it exists?
If I understand correctly, you are looking for Delegate.CreateDelegate:
var alwaysReturnTrueMethodInfo = typeof(YourClass).GetMethod("AlwaysReturnTrue").MakeGenericMethod(someVariableType);
Delegate validateFunc;
if (validateOfType == null)
{
validateFunc = Delegate.CreateDelegate(validateFuncType, alwaysReturnTrueMethodInfo);
}
else
{
validateFunc = Delegate.CreateDelegate(validateFuncType, validateOfType);
}
where AlwaysReturnTrue is a helper static method declared like this:
public static bool AlwaysReturnTrue<T>(T t) { return true }
You can do it either by:
// create an object of the type
var obj = Activator.CreateInstance(validateFuncType);
And you'll get an instance of validateFuncType in obj.
Another way is to use reflection:
// get public constructors
var ctors = validateFuncType.GetConstructors(BindingFlags.Public);
// invoke the first public constructor with no parameters.
var obj = ctors[0].Invoke(new object[] { });
This was taken from this SO answer. And because of that this question (and answer) might be marked as duplicate.
What I did, after noticing that input parameters for Func<> are contravariant.
object validateFunc = validateOfType != null
? config => (bool)validateOfType.Invoke(config, new object[0])
: new Func<object, bool>(_ => true);
I'm not sure if this is better than #Sweeper's 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
Following an answer fulfilling the question in part here is some additional information to hopefully solve the points still at issue
Start edit
var curEntityPI = ctx.GetType().GetProperties().Where(pr => pr.Name == "Client").First();
Type curEntityType = curEntityPI.PropertyType.GetGenericArguments().First();
Type[] typeArgs = { curEntityType };
Type propertyManagerType = generic.MakeGenericType(typeArgs);
var propertyManager = Activator.CreateInstance(propertyManagerType, new object[] {});
with this in mind i can't use the closeMethod.Invoke in the same way displayed in the first answer and it is the Func and return body that I don't know how to put in place when invoking
End edit
What should the method signature look like to reflection, I'm trying to invoke the equivalent of this
DynamicPropertyManager<ThreeColumns>.CreateProperty<ThreeColumns, string>(
"Four",
t => "Four",
null
));
on this class found here http://putridparrot.com/blog/dynamically-extending-an-objects-properties-using-typedescriptor/
But I'm trying to do it using reflection. What I'm struggling with
the most is getting the correct method overload.
I have to be honest though I'm also not totally sure how to supply the correct argument for the lambda bit through reflection
either.
I was going to try this in part but don't know what the func bit
would look like when doing MakeGenericMethod
Func<string> funcArg = () => { return "Four"; };
object[] args = { fieldOrPropertyName , funcArg, null };
The class contents from the link above are included for reference.
public class DynamicPropertyManager<TTarget> : IDisposable
{
private readonly DynamicTypeDescriptionProvider provider;
private readonly TTarget target;
public DynamicPropertyManager()
{
Type type = typeof(TTarget);
provider = new DynamicTypeDescriptionProvider(type);
TypeDescriptor.AddProvider(provider, type);
}
public DynamicPropertyManager(TTarget target)
{
this.target = target;
provider = new DynamicTypeDescriptionProvider(typeof(TTarget));
TypeDescriptor.AddProvider(provider, target);
}
public IList<PropertyDescriptor> Properties
{
get { return provider.Properties; }
}
public void Dispose()
{
if (ReferenceEquals(target, null))
{
TypeDescriptor.RemoveProvider(provider, typeof(TTarget));
}
else
{
TypeDescriptor.RemoveProvider(provider, target);
}
}
public static DynamicPropertyDescriptor<TTargetType, TPropertyType>
CreateProperty<TTargetType, TPropertyType>(
string displayName,
Func<TTargetType, TPropertyType> getter,
Action<TTargetType, TPropertyType> setter,
Attribute[] attributes)
{
return new DynamicPropertyDescriptor<TTargetType, TPropertyType>(
displayName, getter, setter, attributes);
}
public static DynamicPropertyDescriptor<TTargetType, TPropertyType>
CreateProperty1<TTargetType, TPropertyType>(
string displayName,
Func<TTargetType, TPropertyType> getHandler,
Attribute[] attributes)
{
return new DynamicPropertyDescriptor<TTargetType, TPropertyType>(
displayName, getHandler, (t, p) => { }, attributes);
}
public static DynamicPropertyDescriptor<TTargetType, TPropertyType>
CreateProperty<TTargetType, TPropertyType>(
string displayName,
Func<TTargetType, TPropertyType> getHandler,
Attribute[] attributes)
{
return new DynamicPropertyDescriptor<TTargetType, TPropertyType>(
displayName, getHandler, (t, p) => { }, attributes);
}
}
Reflection and generics are working very well together, but how to approach a specific goal is very context dependant, because of possibly closed, open and partially closed types and methods. Nonetheless often it is easy to get what you are looking for by using Linq. Have a look:
// get type from somewhere
var compileTimeUnknownType = Type.GetType("ThreeColumns");
if (compileTimeUnknownType == null)
throw new ArgumentException("compileTimeUnknownType");
var managerType = typeof (DynamicPropertyManager<>).MakeGenericType(compileTimeUnknownType);
var createPropertyMethod = managerType.GetMethods().Single(x =>
{
var p = x.GetParameters();
var g = x.GetGenericArguments();
return x.Name == "CreateProperty" &&
p.Length == 3 &&
g.Length == 2 &&
p[0].ParameterType == typeof (string) &&
p[1].ParameterType == typeof (Func<,>).MakeGenericType(g) &&
p[2].ParameterType == typeof (Attribute[]);
});
var closedMethod = createPropertyMethod.MakeGenericMethod(new[] {compileTimeUnknownType, typeof (string)});
var paramExpr = Expression.Parameter(compileTimeUnknownType, "arg");
var lambda =
Expression.Lambda(typeof (Func<,>).MakeGenericType(new[] {compileTimeUnknownType, typeof (string)}),
Expression.Constant("Four"), new List<ParameterExpression>() {paramExpr}).Compile();
var ret = closedMethod.Invoke(null, new object[] {"Four", lambda, null});
I have this static class with Specifications:
public static class OperationSpecs
{
public static ISpecification<TestEntity> TestSpec = new Specification<TestEntity>(
o =>
{
return (o.Param1== 1 &&
o.Param2== 3
);
}
);
Specification implementation:
public class Specification<T> : ISpecification<T>
{
private Func<T, bool> expression;
public Specification(Func<T, bool> expression)
{
if (expression == null)
throw new ArgumentNullException();
else
this.expression = expression;
}
public bool IsSatisfiedBy(T o)
{
return this.expression(o);
}
}
How can I call TestSpec.IsSatisfiedBy(someType) using reflection ? I tried this:
var specAttr = op.GetCustomAttribute<OperationSpecificationAttribute>();
var specType = specAttr.SpecificationType;
var specTypeMethodName = specAttr.SpecificationMethodName;
var specField = specType.GetField(specTypeMethodName, BindingFlags.Public | BindingFlags.Static);
if (specField != null)
{
var specFieldType = specField.FieldType;
var result2 = specField.GetValue(null).GetType().GetMethod("IsSatisfiedBy").Invoke(null, new object[] { entity });
}
I got ERROR when call Invoke Non-static method requires a target ... I need to get boolean result..
Thanks for Help!
You are trying to invoke the method IsSatisfiedBy using reflection. In contradiction to your title, this method is NOT a static method, it is an instance method. You you need to invoke the method WITH it's instance:
var instance = specField.GetValue(null);
var instanceType = instance.GetType();
var methodInfo = instanceType.GetMethod("IsSatisfiedBy");
var result2 = methodInfo.Invoke(instance, new object[] { entity }); // <<-- instance added.
or in short:
var instance = specField.GetValue(null);
var result2 = instance.GetType().GetMethod("IsSatisfiedBy").Invoke(instance, new object[] { entity });
I'm trying to write a class that's to be used to trigger a call to a method from an arbitrary event but I'm stuck as I simply cannot figure out a way to reference 'this' from emitted MSIL code.
This example should describe what I'm looking for:
class MyEventTriggeringClass
{
private object _parameter;
public void Attach(object source, string eventName, object parameter)
{
_parameter = parameter;
var e = source.GetType().GetEvent(eventName);
if (e == null) return;
hookupDelegate(source, e);
}
private void hookupDelegate(object source, EventInfo e)
{
var handlerType = e.EventHandlerType;
// (omitted some validation here)
var dynamicMethod = new DynamicMethod("invoker",
null,
getDelegateParameterTypes(handlerType), // (omitted this method in this exmaple)
GetType());
var ilgen = dynamicMethod.GetILGenerator();
var toBeInvoked = GetType().GetMethod(
"invokedMethod",
BindingFlags.NonPublic | BindingFlags.Instance);
ilgen.Emit(OpCodes.Ldarg_0); // <-- here's where I thought I could push 'this' (failed)
ilgen.Emit(OpCodes.Call, toBeInvoked);
ilgen.Emit(OpCodes.Ret);
var sink = dynamicMethod.CreateDelegate(handlerType);
e.AddEventHandler(source, sink);
}
private void invokedMethod()
{
Console.WriteLine("Value of _parameter = " + _parameter ?? "(null)");
// output is always "(null)"
}
}
Here's an xample how I envision the class being used:
var handleEvent = new MyEventTriggeringClass();
handleEvent.Attach(someObject, "SomeEvent", someValueToBePassedArround);
(Please note that the above example is quite pointless. I just try to describe what I'm looking for. My final goal here is to be able to trigger a call to an arbitrary method whenever an arbitrary event fires. I'll use that in a WPF projekt where I try to use 100% MVVM but I've stumbled upon one of the [seemingly] classic breaking points.)
Anyway, the code "works" so far as it successfully invoked the "invokedMethod" when the arbitrary event fires but 'this' seems to be an empty object (_parameter is always null). I have done some research but simply cannot find any good examples where 'this' is properly passed to a method being called from within a dynamic method like this.
The closest example I've found is THIS ARTICLE but in that example 'this' can be forced to the dynamic method since it's called from the code, not an arbitrary event handler.
Any suggestions or hints would be very appreciated.
Because of the way variance on delegates works in .Net, you can write the code in C# without using codegen:
private void InvokedMethod(object sender, EventArgs e)
{
// whatever
}
private MethodInfo _invokedMethodInfo =
typeof(MyEventTriggeringClass).GetMethod(
"InvokedMethod", BindingFlags.Instance | BindingFlags.NonPublic);
private void hookupDelegate(object source, EventInfo e)
{
Delegate invokedMethodDelegate =
Delegate.CreateDelegate(e.EventHandlerType, this, _invokedMethodInfo);
e.AddEventHandler(source, invokedMethodDelegate);
}
To explain, let's say you have some event that follows the standard event pattern, that is, return type is void, first parameter is object and second parameter is EventArgs or some type derived from EventArgs. If you have that and InvokeMethod defined as above, you can write someObject.theEvent += InvokedMethod. This is allowed because it is safe: you know the second parameter is some type that can act as EventArgs.
And the code above is basically the same, except using reflection when given the event as EventInfo. Just create a delegate of the correct type that references our method and subscribe to the event.
If you're sure you want to go with the codegen way, possibly because you want to support non-standard events too, you could do it like this:
Whenever you want to attach to an event, create a class that has a method that matches the event's delegate type. The type will also have a field that holds the passed-in parameter. (Closer to your design would be a field that holds a reference to the this instance of MyEventTriggeringClass, but I think it makes more sense this way.) This field is set in the constructor.
The method will call invokedMethod, passing parameter as a parameter. (This means invokedMethod has to be public and can be made static, if you don't have another reason to keep in non-static.)
When we're done creating the class, create an instance of it, create a delegate to the method and attach that to the event.
public class MyEventTriggeringClass
{
private static readonly ConstructorInfo ObjectCtor =
typeof(object).GetConstructor(Type.EmptyTypes);
private static readonly MethodInfo ToBeInvoked =
typeof(MyEventTriggeringClass)
.GetMethod("InvokedMethod",
BindingFlags.Public | BindingFlags.Static);
private readonly ModuleBuilder m_module;
public MyEventTriggeringClass()
{
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName("dynamicAssembly"),
AssemblyBuilderAccess.RunAndCollect);
m_module = assembly.DefineDynamicModule("dynamicModule");
}
public void Attach(object source, string #event, object parameter)
{
var e = source.GetType().GetEvent(#event);
if (e == null)
return;
var handlerType = e.EventHandlerType;
var dynamicType = m_module.DefineType("DynamicType" + Guid.NewGuid());
var thisField = dynamicType.DefineField(
"parameter", typeof(object),
FieldAttributes.Private | FieldAttributes.InitOnly);
var ctor = dynamicType.DefineConstructor(
MethodAttributes.Public, CallingConventions.HasThis,
new[] { typeof(object) });
var ctorIL = ctor.GetILGenerator();
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Call, ObjectCtor);
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Ldarg_1);
ctorIL.Emit(OpCodes.Stfld, thisField);
ctorIL.Emit(OpCodes.Ret);
var dynamicMethod = dynamicType.DefineMethod(
"Invoke", MethodAttributes.Public, typeof(void),
GetDelegateParameterTypes(handlerType));
var methodIL = dynamicMethod.GetILGenerator();
methodIL.Emit(OpCodes.Ldarg_0);
methodIL.Emit(OpCodes.Ldfld, thisField);
methodIL.Emit(OpCodes.Call, ToBeInvoked);
methodIL.Emit(OpCodes.Ret);
var constructedType = dynamicType.CreateType();
var constructedMethod = constructedType.GetMethod("Invoke");
var instance = Activator.CreateInstance(
constructedType, new[] { parameter });
var sink = Delegate.CreateDelegate(
handlerType, instance, constructedMethod);
e.AddEventHandler(source, sink);
}
private static Type[] GetDelegateParameterTypes(Type handlerType)
{
return handlerType.GetMethod("Invoke")
.GetParameters()
.Select(p => p.ParameterType)
.ToArray();
}
public static void InvokedMethod(object parameter)
{
Console.WriteLine("Value of parameter = " + parameter ?? "(null)");
}
}
This is still doesn't take care of all possible events, though. That's because the delegate of an event can have a return type. That would mean giving a return type to the generated method and returning some value (probably default(T)) from it.
There's (at least) one possible optimization: don't create a new type every time, but cache them. When you try to attach to an event with the same signature as a previous one, use use its class.
I'm gonna go ahead and answer my own question here. The solution was very simple once I realized what the real problem was: Specifying the event handler's instance/target. This is done by adding an argument to MethodInfo.CreateDelegate().
If you're interested, here's a simple example you can cut'n'paste into a console app and try it out:
class Program
{
static void Main(string[] args)
{
var test = new MyEventTriggeringClass();
var eventSource = new EventSource();
test.Attach(eventSource, "SomeEvent", "Hello World!");
eventSource.RaiseSomeEvent();
Console.ReadLine();
}
}
class MyEventTriggeringClass
{
private object _parameter;
public void Attach(object eventSource, string eventName, object parameter)
{
_parameter = parameter;
var sink = new DynamicMethod(
"sink",
null,
new[] { typeof(object), typeof(object), typeof(EventArgs) },
typeof(Program).Module);
var eventInfo = typeof(EventSource).GetEvent("SomeEvent");
var ilGenerator = sink.GetILGenerator();
var targetMethod = GetType().GetMethod("TargetMethod", BindingFlags.Instance | BindingFlags.Public, null, new Type[0], null);
ilGenerator.Emit(OpCodes.Ldarg_0); // <-- loads 'this' (when sink is not static)
ilGenerator.Emit(OpCodes.Call, targetMethod);
ilGenerator.Emit(OpCodes.Ret);
// SOLUTION: pass 'this' as the delegate target...
var handler = (EventHandler)sink.CreateDelegate(eventInfo.EventHandlerType, this);
eventInfo.AddEventHandler(eventSource, handler);
}
public void TargetMethod()
{
Console.WriteLine("Value of _parameter = " + _parameter);
}
}
class EventSource
{
public event EventHandler SomeEvent;
public void RaiseSomeEvent()
{
if (SomeEvent != null)
SomeEvent(this, new EventArgs());
}
}
So, thanks for your comments and help. Hopefully someone learned something. I know I did.
Cheers
Here is my own version / for my own needs:
/// <summary>
/// Corresponds to
/// control.Click += new EventHandler(method);
/// Only done dynamically, and event arguments are omitted.
/// </summary>
/// <param name="objWithEvent">Where event resides</param>
/// <param name="objWhereToRoute">To which object to perform execution to</param>
/// <param name="methodName">Method name which to call.
/// methodName must not take any parameter in and must not return any parameter. (.net 4.6 is strictly checking this)</param>
private static void ConnectClickEvent( object objWithEvent, object objWhereToRoute, string methodName )
{
EventInfo eventInfo = null;
foreach (var eventName in new String[] { "Click" /*WinForms notation*/, "ItemClick" /*DevExpress notation*/ })
{
eventInfo = objWithEvent.GetType().GetEvent(eventName);
if( eventInfo != null )
break;
}
Type objWhereToRouteObjType = objWhereToRoute.GetType();
var method = eventInfo.EventHandlerType.GetMethod("Invoke");
List<Type> types = method.GetParameters().Select(param => param.ParameterType).ToList();
types.Insert(0, objWhereToRouteObjType);
var methodInfo = objWhereToRouteObjType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null);
if( methodInfo.ReturnType != typeof(void) )
throw new Exception("Internal error: methodName must not take any parameter in and must not return any parameter");
var dynamicMethod = new DynamicMethod(eventInfo.EventHandlerType.Name, null, types.ToArray(), objWhereToRouteObjType);
ILGenerator ilGenerator = dynamicMethod.GetILGenerator(256);
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.EmitCall(OpCodes.Call, methodInfo, null);
ilGenerator.Emit(OpCodes.Ret);
var methodDelegate = dynamicMethod.CreateDelegate(eventInfo.EventHandlerType, objWhereToRoute);
eventInfo.AddEventHandler(objWithEvent, methodDelegate);
} //ConnectClickEvent