How to call method with EventHandler parameter using expression trees? - c#

Consider this simple piece of code. How can this be done using expression trees?
ErrorsChangedEventManager.AddHandler(obj, obj.SomeHandler);
Here's a small sample illustrating what I'm trying to accomplish. (Add a reference to WindowBase to make it compile.)
class Program : INotifyDataErrorInfo
{
public int Id { get; set; }
static void Main(string[] args)
{
var p1 = new Program { Id = 1 };
var p2 = new Program { Id = 2 };
// Here is the root of the problem.
// I need to do this INSIDE the expression from a given instance of Program.
EventHandler<DataErrorsChangedEventArgs> handler = p1.OnError;
var handlerConstant = Expression.Constant(handler);
var mi = typeof(ErrorsChangedEventManager).GetMethod(nameof(ErrorsChangedEventManager.AddHandler),
BindingFlags.Public | BindingFlags.Static);
var source = Expression.Parameter(typeof(INotifyDataErrorInfo), "source");
var program = Expression.Parameter(typeof(Program), "program");
// This will work, but the OnError method will be invoked on the wrong instance.
// So, I need to get the expression to perform what would otherwise be easy in code...
// E.g. AddHandler(someObject, p2.OnError);
var call = Expression.Call(mi, source, handlerConstant);
var expr = Expression.Lambda<Action<INotifyDataErrorInfo, Program>>(call, source, program);
var action = expr.Compile();
action.DynamicInvoke(p1, p2);
p1.ErrorsChanged.Invoke(p1, new DataErrorsChangedEventArgs("Foo"));
}
void OnError(object sender, DataErrorsChangedEventArgs e)
{
if (sender is Program p)
{
Console.WriteLine($"OnError called for Id={Id}. Expected Id=2");
}
}
public IEnumerable GetErrors(string propertyName) => Enumerable.Empty<string>();
public bool HasErrors => false;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}
Obviously, it doesn't work. I somehow need to provide the OnError handler as a parameter to the call.

It seems the easiest thing to do is to create a lambda which creates the EventHandler<DataErrorsChangedEventArgs> for you, and then use Expression.Invoke to call it:
public class Program : INotifyDataErrorInfo
{
public int Id { get; set; }
public static void Main()
{
var p1 = new Program { Id = 1 };
var p2 = new Program { Id = 2 };
var mi = typeof(ErrorsChangedEventManager).GetMethod(nameof(ErrorsChangedEventManager.AddHandler),
BindingFlags.Public | BindingFlags.Static);
var source = Expression.Parameter(typeof(INotifyDataErrorInfo), "source");
var program = Expression.Parameter(typeof(Program), "program");
Expression<Func<Program, EventHandler<DataErrorsChangedEventArgs>>> createDelegate = p => p.OnError;
var createDelegateInvoke = Expression.Invoke(createDelegate, program);
var call = Expression.Call(mi, source, createDelegateInvoke);
var expr = Expression.Lambda<Action<INotifyDataErrorInfo, Program>>(call, source, program);
var action = expr.Compile();
action(p1, p2);
p1.ErrorsChanged.Invoke(p1, new DataErrorsChangedEventArgs("Foo"));
}
public void OnError(object sender, DataErrorsChangedEventArgs e)
{
if (sender is Program p)
{
Console.WriteLine($"OnError called for Id={Id}. Expected Id=2");
}
}
public IEnumerable GetErrors(string propertyName) => Enumerable.Empty<string>();
public bool HasErrors => false;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}
If you look at the DebugView for createDelegate, you can see that the compiler created:
.Lambda #Lambda1<System.Func`2[Program,System.EventHandler`1[System.ComponentModel.DataErrorsChangedEventArgs]]>(Program $p)
{
(System.EventHandler`1[System.ComponentModel.DataErrorsChangedEventArgs]).Call .Constant<System.Reflection.MethodInfo>(Void OnError(System.Object, System.ComponentModel.DataErrorsChangedEventArgs)).CreateDelegate(
.Constant<System.Type>(System.EventHandler`1[System.ComponentModel.DataErrorsChangedEventArgs]),
$p)
}
You could construct this expression yourself if you wanted, by getting the MethodInfo for OnError, then calling CreateDelegate on it.
All of that said, you can just use a lambda to do all of that:
Expression<Action<INotifyDataErrorInfo, Program>> test = (source, program) =>
ErrorsChangedEventManager.AddHandler(source, program.OnError);

Related

Add and remove event handler via reflection c#

Good day!
My purpose is to implement class which will allow us subscribe and unsubscribe objects to(from) events. Here is the code of my class.
public static class EventSubscriber
{
public static void AddEventHandler(EventInfo eventInfo, object item, Action action)
{
var parameters = GetParameters(eventInfo);
var handler = GetHandler(eventInfo, action, parameters);
eventInfo.AddEventHandler(item, handler);
}
public static void RemoveEventHandler(EventInfo eventInfo,
object item, Action action)
{
var parameters = GetParameters(eventInfo);
var handler = GetHandler(eventInfo, action, parameters);
eventInfo.RemoveEventHandler(item, handler);
}
private static ParameterExpression[] GetParameters(EventInfo eventInfo)
{
return eventInfo.EventHandlerType
.GetMethod("Invoke")
.GetParameters()
.Select(parameter => Expression.Parameter(parameter.ParameterType))
.ToArray();
}
private static Delegate GetHandler(EventInfo eventInfo,
Action action, ParameterExpression[] parameters)
{
return Expression.Lambda(
eventInfo.EventHandlerType,
Expression.Call(Expression.Constant(action),
"Invoke", Type.EmptyTypes), parameters)
.Compile();
}
}
As you can see here are 2 public methods which actually subscribe and unsubscribe objects to(from) event. And here is the sample how I test it
class Program
{
static void Main()
{
Test test = new Test();
test.SubscribeTimer();
while (true)
{
if(test.a == 10)
{
break;
}
}
test.UnsubscribeTimer();
while (true)
{
}
}
}
class Test
{
System.Timers.Timer timer;
public int a = 0;
public Test()
{
timer = new System.Timers.Timer(1000);
timer.Start();
}
public void SubscribeTimer()
{
var eventInfo = typeof(System.Timers.Timer).GetEvent("Elapsed");
EventSubscriber.AddEventHandler(eventInfo, timer, TimerElapsed);
EventSubscriber.RemoveEventHandler(eventInfo, timer, TimerNotElapsed);
}
public void UnsubscribeTimer()
{
var eventInfo = typeof(System.Timers.Timer).GetEvent("Elapsed");
EventSubscriber.AddEventHandler(eventInfo, timer, TimerNotElapsed);
EventSubscriber.RemoveEventHandler(eventInfo, timer, TimerElapsed);
}
public void TimerElapsed()
{
Console.WriteLine("timer elapsed");
a++;
}
public void TimerNotElapsed()
{
Console.WriteLine("timer not elapsed");
a++;
}
}
The expected behaviour of sample is that on the begining we will see the message "timer elapsed" every second, after 10-th second we should see only "timer not elapsed" and we do, but we still see "timer elapsed" too. This means that AddEventHandler method works, but RemoveEventHandler method doesn't.
I would be very happy if you will help me. Thanks in advance.
Had to stitch a bunch of sources together to get what I needed which was a one-stop extension method (that could be easily modified if we switched component vendors) to simply clear all existing Event Handlers on an Event we did not control.
Usage is simple:
thirdPartyControlInstance.ClearEventHandlers(nameof(ThirdPartyControlType.ToolClick));
Here is the code, any suggestions to make it more robust/efficient/cleaner are welcome:
public static class EventExtensions
{
public static void ClearEventHandlers(this object obj, string eventName)
{
if (obj == null)
{
return;
}
var objType = obj.GetType();
var eventInfo = objType.GetEvent(eventName);
if (eventInfo == null)
{
return;
}
var isEventProperty = false;
var type = objType;
FieldInfo eventFieldInfo = null;
while (type != null)
{
/* Find events defined as field */
eventFieldInfo = type.GetField(eventName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (eventFieldInfo != null && (eventFieldInfo.FieldType == typeof(MulticastDelegate) || eventFieldInfo.FieldType.IsSubclassOf(typeof(MulticastDelegate))))
{
break;
}
/* Find events defined as property { add; remove; } */
eventFieldInfo = type.GetField("EVENT_" + eventName.ToUpper(), BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic);
if (eventFieldInfo != null)
{
isEventProperty = true;
break;
}
type = type.BaseType;
}
if (eventFieldInfo == null)
{
return;
}
if (isEventProperty)
{
// Default Events Collection Type
RemoveHandler<EventHandlerList>(obj, eventFieldInfo);
// Infragistics Events Collection Type
RemoveHandler<EventHandlerDictionary>(obj, eventFieldInfo);
return;
}
if (!(eventFieldInfo.GetValue(obj) is Delegate eventDelegate))
{
return;
}
// Remove Field based event handlers
foreach (var d in eventDelegate.GetInvocationList())
{
eventInfo.RemoveEventHandler(obj, d);
}
}
private static void RemoveHandler<T>(object obj, FieldInfo eventFieldInfo)
{
var objType = obj.GetType();
var eventPropertyValue = eventFieldInfo.GetValue(obj);
if (eventPropertyValue == null)
{
return;
}
var propertyInfo = objType.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance)
.FirstOrDefault(p => p.Name == "Events" && p.PropertyType == typeof(T));
if (propertyInfo == null)
{
return;
}
var eventList = propertyInfo?.GetValue(obj, null);
switch (eventList) {
case null:
return;
case EventHandlerDictionary typedEventList:
typedEventList.RemoveHandler(eventPropertyValue, typedEventList[eventPropertyValue]);
break;
}
}
}
Hope this helps someone!
I think it's because you are creating a new handler each time: (which doesn't match the previous handler, so can't be removed from the invocation list)
public static void RemoveEventHandler(EventInfo eventInfo,
object item, Action action)
{
var parameters = GetParameters(eventInfo);
var handler = GetHandler(eventInfo, action, parameters); // <--
eventInfo.RemoveEventHandler(item, handler);
}
Why are you wrapping the Action? To lose the parameters? It is not possible to add/remove the eventInfo.RemoveEventHandler(item, action); because of the parameters. If you want to remove a newly generated handler, you should return that handler when you want to remove it.
public static Delegate AddEventHandler(EventInfo eventInfo, object item, Action action)
{
var parameters = GetParameters(eventInfo);
var handler = GetHandler(eventInfo, action, parameters);
eventInfo.AddEventHandler(item, handler);
return handler;
}
public static void RemoveEventHandler(EventInfo eventInfo,
object item, Delegate handler)
{
eventInfo.RemoveEventHandler(item, handler);
}
var handler = EventSubscriber.AddEventHandler(eventInfo, timer, TimerElapsed);
EventSubscriber.RemoveEventHandler(eventInfo, timer, handler);

Accessing a delegate via reflection declared outside of any class?

I am trying a demo for reflection. The assembly I want to reflect in this class goes something like this
namespace DelegatesSampleApplication
{
delegate bool IsPromotable (Employee employee); // Declaration Syntax is similar to that of a method's
class Program
{
public static void Main(string[] args)
{
//code goes here
}
}
class Employee
{
public int EmployeeId { get; set; }
public string EmployeeName { get; set; }
public int Salary { get; set; }
public int Experience { get; set; }
public void PromoteEmployees(List<Employee> employeeList, IsPromotable isPromotableObj)
{
/*foreach (Employee employee in employeeList)
{
if (employee.Experience >= 5 && employee.Salary >= 10000) //That's a hard-coded logic that you have developed as a Framework Developer which makes the class itself not reusable
{
Console.WriteLine(" {0} will be promoted in the next R.P. Cycle ", employee.EmployeeName);
}
}
Console.ReadKey();*/
foreach (Employee employee in employeeList)
{
if (isPromotableObj(employee))
{
Console.WriteLine(" {0} will be promoted in the next R.P. Cycle ", employee.EmployeeName);
}
}
Console.ReadKey();
}
}
}
Now the problem I am facing is, I am trying to read from this assembly in my program and trying to invoke the delegate which takes in a class instance as a parameter.
What I am doing is something like this in a different class altogether
namespace ReflectionSample
{
delegate bool empIsPromotable (Object obj);
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***************Loading External assembly*************");
Assembly assembly = Assembly.LoadFrom(#"C:\Users\Chiranjib\Documents\visual studio 2012\Projects\DelegatesSampleApplication\DelegatesSampleApplication\bin\Debug\DelegatesSampleApplication.exe");
Type employeeType = assembly.GetType("DelegatesSampleApplication.Employee"); //Gets the System.Type object for the Employee Class from the just loaded assembly with all it's dependencies
Console.WriteLine("***************Loading External assembly properties*************");
//Setting properties here
Console.WriteLine("***************Creating an array list that will hold these employee instances***************");
List<Object> employeeInstanceList = new List<object>();
employeeInstanceList.Add(employeeInstance);
employeeInstanceList.Add(employeeInstance2);
Console.WriteLine("***************Invoking External assembly methods*************");
var args = new Object[] {
new List<employeeType>(),
(((employeeInstance) => { return true; }))
};
Console.ReadKey();
}
}
}
This throws error. Saying employeeType missing an assembly reference. Also I cannot convert a lambda expression into an object. I cannot directly type cast to (IsPromotable) right ? I am using reflection. So I am supposed not to have a direct access.
How would I be able to access the delegate via reflection ?
Please help me through. Thanks in advance.
IsPromotable is not invokeable, it's simply a delegate definition. Think of it like an interface. It just tells you what the method takes and returns, but does not actually do anything.
You can call PromoteEmployees like this:
PromoteEmployees(new List<Employee>, (employee) => { return employee.Name == "Rob"; });
Any method which matches the signature of IsPromotable is a valid parameter to the method (ie, any method that takes an Employee and returns a bool). Could you elaborate a bit more on what you're trying to do, exactly?
If you just want a list of delegates, you can do this:
GetType().Assembly.GetTypes().Where(t => t.IsSubClassOf(typeof(BaseDelegate)));
Note; this will only return the delegates in your current assembly. You might want to change it to:
typeof(Employee).GetTypes().Assembly.Where(t => t.IsSubClassOf(typeof(BaseDelegate)));
To get all methods in an assembly which are castable to IsPromotable, you can do this:
var delegateMethod = typeof(IsPromotable).GetMethod("Invoke");
var #params = delegateMethod.GetParameters();
var returnType = delegateMethod.ReturnType;
var matchingMethods = typeof(IsPromotable)
.Assembly
.GetTypes()
.SelectMany(t => t.GetMethods())
.Where(m => {
if (m.ReturnType != returnType)
return false;
var currParams = m.GetParameters();
if (currParams.Length != #params.Length)
return false;
for(var i = 0; i < currParams.Length;i++)
if (currParams[i] != #params[i])
return false;
return true;
});
To invoke the method with reflection, you can do this:
var args = new Object[] {
new List<Employee>(),
((IsPromotable)((emp) => { return true; }))
};
var value = employeeType.InvokeMember("PromoteEmployees", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, employeeInstance, args);
Or, you can just pass it a regular method:
var args = new Object[] {
new List<Employee>(),
((IsPromotable)Test)
};
var value = employeeType.InvokeMember("PromoteEmployees", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, employeeInstance, args);
With the method:
private bool Test(Employee emp)
{
return false;
}

wrap anonymous function with another call

I have this code in which I am trying to wrap an anonymous function by another call so when it is actually the wrapper's value that is returned and not the original function's.
In the code below however I get a NullPointerException in the last call. I am sure I am not doing something right, but beats me what it is.
class Program
{
public class MyClass
{
public int MyProp { get; set; }
}
private static List<Func<MyClass, object>> Calls;
private static object ExtraCall(int obj)
{
var returnVal = 8;
//do some stuff
return returnVal;
}
static void Main(string[] args)
{
Calls = new List<Func<MyClass, object>>();
Calls.Add(c => c.MyProp);
Func<MyClass, object> func = c => c.MyProp;
Calls.Add(c => ExtraCall((int)func(func.Target as MyClass)));
var obj = new MyClass(){ MyProp = 7 };
var test1 = Calls[0](obj);
var test2 = Calls[1](obj);
}
}
func.Target is null because this delegate doesn't have any instance which it is invoked on. You can try following code:
Calls.Add(c => ExtraCall((int)func(c)));

How can I call a reflected Func<T, T> property using Expression Trees

I have a generic class with a lambda property defined as such:
public class Transformation<TProperty> : TransformationBase
{
public Func<TProperty, TProperty> Transform { get; private set; }
...
I'm trying to compile an Action that can call this Transform property (on a property of Foo). I don't know TProperty at compile-time. I've started with this:
private static Action<Foo> Compile(Transformation transformation)
{
var fooParameter = Expression.Parameter(typeof(Foo));
var changePropertyValue = Expression.Constant(transformation);
var transformProperty = Expression.Property(changePropertyValue, "Transform");
var transfromCall = Expression.Call(transformProperty, ?
}
How can I call/execute the transformProperty?
EDIT: Foo (which is known a compile time) has an untyped property Value which needs to be transformed using the Transform property of the Transformation:
public class Foo {
public object Value { get; set; }
}
So, hand-written as an example where TProperty is string it would be:
Foo foo = ... // coming from an external source
Transformation<string> tranformation = ... // coming from an external source
foo.Value = transformation.Transform((string)foo.Value);
Except that I don't know the exact type of the Transformation as it is defined in an external assembly. So, instead of string it could be int or something else. That's why I want to use Expression Trees to compile an Action for a given transformation, such that I can call:
Foo foo = ... // coming from an external source
TransformationBase transformation = ... // coming from an external source
Action<Foo> transform = Compile(transformation);
transform(foo); // should transform foo.Value using the Transform property of 'transformation'
Note: I made Transformation inherit from TransformationBase to clarify this discussion.
Your problems relate more to the lack of typing around your problem. Foo.Value is loosely typed, but your transform functions are strongly typed. Expression Trees are also strongly typed. Using them doesn't allow you to magically call code in a loosely typed manner.
The solution is either a lot of reflection, or some easy dynamic:
EDIT: I added CompileUntyped which uses ExpressionTrees.I also added CompileReflection, which uses Reflection without ExpressionTrees. I would recommend the one that uses dynamic. It is by far the easiest to read, hence the easiest to maintain.
class Program
{
static void Main(string[] args)
{
var testTransform = new Transformation<string>
{
Transform = s => s.ToUpper()
};
var a = Compile(testTransform);
var foo = new Foo
{
Value = "test"
};
a(foo);
//foo.Value is now TEST
}
public static Action<Foo> CompileReflection(TransformationBase transformation)
{
var f = transformation
.GetType()
.GetProperty("Transform")
.GetGetMethod()
.Invoke(transformation, null) as Delegate;
return foo => foo.Value = f.DynamicInvoke(foo.Value);
}
public static Action<Foo> Compile(TransformationBase transformation)
{
return new Action<Foo>(f =>
{
dynamic d = f.Value;
dynamic t = transformation;
f.Value = t.Transform(d);
});
}
public static Action<Foo> CompileUntyped(TransformationBase transformation)
{
var transformType = transformation.GetType();
var genericType = transformType.GetGenericArguments().First();
var fooParam = Expression.Parameter(typeof(Foo), "f");
var valueGetter = typeof(Foo).GetProperty("Value").GetGetMethod();
var valueSetter = typeof(Foo).GetProperty("Value").GetSetMethod();
var transformFuncMember = transformType.GetProperty("Transform").GetGetMethod();
//Equivalent to f => f.Value = transformation.Transform((T)f.Value)
//Where T is the generic type parameter of the Transformation, and f is of type Foo
var expression = Expression.Lambda<Action<Foo>>(
Expression.Call(
fooParam,
valueSetter,
Expression.Invoke(
Expression.Property(
Expression.Constant(transformation, transformType),
transformFuncMember
),
Expression.Convert(
Expression.Property(fooParam, valueGetter),
genericType
)
)
), fooParam
);
return expression.Compile();
}
}
public class TransformationBase { }
public class Transformation<TProperty> : TransformationBase
{
public Func<TProperty, TProperty> Transform { get; set; }
}
public class Foo
{
public object Value { get; set; }
}
Not sure what are you trying to do BUT if I understand your intentions - I do not see need for compiling Expressions:
private static Action<TProperty> Compile<TProperty>(Transformation<TProperty> transformation)
{
return new Action<TProperty>(p => transformation.Transform(p));
}
See an example, it should give you what you want.
void Main()
{
var dummyObject = new Dummy { Test = "Hello!" };
var propertyTransform = Create(dummyObject, "Test");
propertyTransform(dummyObject);
Console.WriteLine("Final transformation " + dummyObject.Test);
}
class Dummy {
public string Test { get; set; }
}
// Define other methods and classes here
public class Transformation<TProperty>
{
public Func<TProperty, TProperty> Transform { get; set; }
}
public static Action<TObj> Create<TObj>(TObj myObject, string property){
var prop = myObject
.GetType()
.GetProperty(property);
var val = prop.GetValue(myObject);
var transformation = Create((dynamic)val);
var transform = transformation.Transform;
return obj => {
var newValue = transform((dynamic)val);
prop.SetValue(myObject, newValue);
};
}
public static Transformation<TProperty> Create<TProperty>(TProperty property){
var transformation = new Transformation<TProperty>();
// just a dummy hijacking.
if(typeof(TProperty)==typeof(string)){
Func<string, string> test = input => "I am changed man!";
transformation.Transform = (dynamic)test;
}
return transformation;
}
Output:
Final transformation I am changed man!

Create Expression for new Action<T> where T is unknown at compile time

Edit2: removed a load of gubbins + bounty.
I have deconstructed an expression for a message bus in the hope of reconstructing it and invoking it in a slightly different manner. The serialization and deserialization is successful and I am able to create instances of most of what I need.
//Deconstruct
Expression<Func<T, Task>> expression
proxy => proxy.serviceMethod(arg);
I need to create the syntax below. T is an interface to a WCF service. This expression will be passed to a service invoker where it's internal ChannelFactory will pass it into this method.
//Reconstruct this as expression so I can pass it as a parameter
var myAction = new Action<T>(proxy => {
proxy.serviceMethod((SomeType)SomeParameter));
});
// to pass to this method
serviceInvokerTMethod.Invoke(serviceInvokerTInstance, new object[] { myAction });
What I have:
//I think i'm nearly there I can create the inner call and assign
//the correct parameter, but I can't seem to figure out how to wrap it in an
// new Action<serviceT> { my other expressions... }
// Types
var serviceT = Type.GetType(workOutMessage.interfaceType);
var actionT = typeof(Action<>).MakeGenericType(serviceT);
var envelopeT = Type.GetType(workOutMessage.methodArgTypes[0]);
// ServiceInvoker<T> Instantiation - Works
var serviceInvokerT = typeof(HubServiceInvoker<>).MakeGenericType(serviceT);
var serviceInvokerTMethod = serviceInvokerT.GetMethod("InvokeService");
var serviceInvokerTInstance = Activator.CreateInstance(serviceInvokerT, client.Id, "password", clientCert, serviceCert);
// Expression Type Params
var serviceTParam = Expression.Parameter(serviceT, "proxy");
var envelopeParam = Expression.Parameter(envelopeT, "envelope");
var envAssign = Expression.Assign(envelopeParam, Expression.Constant(workOutMessage.methodArgs[0]));
var methodCall = Expression.Call(serviceTParam, serviceT.GetMethod(workOutMessage.methodName), envelopeParam);
// var lambda = ...
// make new Action<serviceT> myAction = { proxy => proxy.someMethod(someParameter); };
serviceInvokerTMethod.Invoke(serviceInvokerTInstance, new object[] { lambda.Compile() });
Edit: The Service Invoker Method I pass this into to, to try and give the problem better context.
public void InvokeService(Action<T> handler)
{
T proxy = channelFactory.CreateChannel();
((IClientChannel)proxy).Faulted += ChannelFaulted;
ICommunicationObject obj2 = (ICommunicationObject)proxy;
try
{
using (new OperationContextScope((IContextChannel)proxy))
{
handler.Invoke(proxy);
}
}
finally
{
try
{
if (obj2.State != CommunicationState.Faulted)
{
obj2.Close();
}
}
catch
{
obj2.Abort();
}
}
}
Here is a full piece of code, in which I assume you only need a Func, not an Action.
using System;
using System.Linq.Expressions;
using System.Threading.Tasks;
namespace HelloWorld
{
public class Service1
{
public Task ServiceMethod(string something)
{
return Task.Factory.StartNew(() => Console.WriteLine(something));
}
}
public class HubServiceInvoker<T> where T : new()
{
T t;
public HubServiceInvoker(string id, string password)
{
t = new T();
}
public void InvokeService(Func<T, Task> serviceInvoker)
{
Task task = serviceInvoker(t);
}
public static Func<T, Task> CompileInvoker(Expression expression, ParameterExpression serviceTParam)
{
Expression<Func<T, Task>> lambda = Expression.Lambda<Func<T, Task>>(expression, serviceTParam);
return lambda.Compile();
}
}
public class WorkOutMessage
{
public string interfaceType { get; set; }
public string[] methodArgTypes { get; set; }
public object[] methodArgs { get; set; }
public string methodName { get; set; }
}
static class Program
{
static void Main(string[] args)
{
WorkOutMessage workOutMessage = new WorkOutMessage()
{
interfaceType = "HelloWorld.Service1",
methodArgTypes = new string[] { "System.String" },
methodArgs = new object[] { "yeah it works!" },
methodName = "ServiceMethod"
};
InvokeService(workOutMessage);
Console.Read();
}
static void InvokeService(WorkOutMessage workOutMessage)
{
// Types
var serviceT = Type.GetType(workOutMessage.interfaceType);
// ServiceInvoker<T> Instantiation - Works
var serviceInvokerT = typeof(HubServiceInvoker<>).MakeGenericType(serviceT);
var serviceInvokerTMethod = serviceInvokerT.GetMethod("InvokeService");
var serviceCompileInvokerTMethod = serviceInvokerT.GetMethod("CompileInvoker");
var serviceInvokerTInstance = Activator.CreateInstance(serviceInvokerT, "id", "password");
// Expression Type Params
var serviceTParam = Expression.Parameter(serviceT, "proxy");
var methodCall = Expression.Call(serviceTParam, serviceT.GetMethod(workOutMessage.methodName), Expression.Constant(workOutMessage.methodArgs[0]));
serviceInvokerTMethod.Invoke(serviceInvokerTInstance, new object[] {
serviceCompileInvokerTMethod.Invoke(serviceInvokerTInstance, new object[] { methodCall, serviceTParam })
});
}
}
}
It was a little bit hard to understand what exactly is going on.
Perhaps, this will help you a little bit further:
// Helper.cs
public static Action<TType> Wrap<TType>(Delegate test)
{
return ret => test.DynamicInvoke();
}
var meth = typeof(Helper).GetMethod("Wrap");
var gmeth = meth.MakeGenericMethod(new[] { serviceT });
var genericAction = gmeth.Invoke(null, new object[] {
Expression.Lambda(methodCall).Compile(); });
Thanks to the hints from #Romain Hautefeuille, the key was to use my generic ServiceInvoker class to help me create the action that I needed without the need of using Expressions (woohoo).
// Execute Interface method from interface, methodName and methodArgs from message queue
var serviceT = Type.GetType(workOutMessage.interfaceType);
var serviceInvokerT = typeof(HubServiceInvoker<>).MakeGenericType(serviceT);
var serviceInvokerTMethod = serviceInvokerT.GetMethod("InvokeService");
var serviceInvokerCompileTMethod = serviceInvokerT.GetMethod("CompileServiceMethod");
var serviceInvokerTInstance = Activator.CreateInstance(serviceInvokerT, client.Id,
"password", clientCert, serviceCert);
// Works! and a lot simpler
serviceInvokerTMethod.Invoke(serviceInvokerTInstance, new object[] {
workOutMessage.correlationId,
serviceInvokerCompileTMethod.Invoke(serviceInvokerTInstance, new object[] {
workOutMessage.methodName,
workOutMessage.methodArgs })
});
And Finally the new method in the ServiceInvoker class (I wanted to avoid reflection in this class - for no particular reason - but it doesn't affect calling it normally).
public Action<T> CompileServiceMethod(string methodName, object[] methodArguments)
{
return new Action<T>(proxy =>
{
typeof(T).GetMethod(methodName).Invoke(proxy, methodArguments);
});
}

Categories