MaYaN
MaYaN

Reputation: 6996

How can I convert C# methods to compiled expressions?

I have the following class hierarchy:

public class Parent
{
    [DebuggerStepThrough]
    public void SayParent()
    {
        Console.WriteLine("Parent");
    }
}

public sealed class Child : Parent 
{
    private static int _number = 0;
    public Child() // May contain parameter i.e. not always parameterless consctructor
    {
        _number++;
    }

    [DebuggerStepThrough]
    public void SayInstance()
    {
        Console.WriteLine("{0}-Say", _number);
    }

    [DebuggerStepThrough]
    public void SayInstanceWithArg(string input)
    {
        Console.WriteLine("{0}-Say: {1}", _number, input);
    }

    [DebuggerStepThrough]
    public static void SayStatic()
    {
        Console.WriteLine("{0}-Say", _number);
    }

    [DebuggerStepThrough]
    public static void SayStaticWithArg(string input)
    {
        Console.WriteLine("{0}-Say: {1}", _number, input);
    }

    [DebuggerStepThrough]
    public static Task SayStaticWithArgAndReturn(string input)
    {
        Console.WriteLine("{0}-Say: {1}", _number, input);
        return null;
    }
}

I need to be able to invoke any of these methods for a new instance of Child at any given time using reflection however to improve performance I need to resort to Delegate and/or Compiled Expressions.

So for example I can have:

var instanceOne = new Child();
var instanceTwo = new Child();

for which I would need to at runtime invoke these methods passing the arguments for those that need it. Note they include both static and instance methods with some accepting a parameter.

I have so far tried the following for the "SayInstance" method:

var sayInstanceMethod = typeof(Child)
        .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
        .Where(m => m.GetCustomAttributes(typeof(DebuggerStepThroughAttribute), true).Length > 0)
        .Where(t => t.Name == "SayInstance")
        .First()

And then:

var instance = Expression.Constant(new Child()); // This should NOT be Constant, but then what should it be?!
var mCallInstance = Expression.Call(instance, sayInstanceMethod);

Action action = Expression.Lambda<Action>(mCallInstance).Compile();

action();
action(); // I need to pass in a new instance of Child to this method somehow

However I am getting:

1-Say
1-Say

instead of:

1-Say 
2-Say

I suspect this is due to Expression.Constant but I cannot figure out how I could let it accept an instance of Child as its target at runtime.

I am hopeless when it comes to Expressions :-(

I am basically trying to implement what Jon Skeet mentions HERE either using Delegates or Compiled Expressions.

Any help is very much appreciated.

Upvotes: 5

Views: 137

Answers (2)

Maksim Simkin
Maksim Simkin

Reputation: 9679

Try this out, this workf for me for a parameterless constructor, but this is that you need:

var instance = Expression.New(typeof(Child).GetConstructor(new Type[0]));
var mCallInstance = Expression.Call(instance, sayInstanceMethod);

Action action = Expression.Lambda<Action>(mCallInstance).Compile();

action();
action(); // I need to pass in a new instance of Child to this method someh

Upvotes: 2

Evk
Evk

Reputation: 101543

If I understood correctly, you need to use parameters, like this:

var instanceOne = new Child();
var instanceTwo = new Child();            
var instance = Expression.Parameter(typeof(Child), "c"); // This should NOT be Constant, but then what should it be?!
var mCallInstance = Expression.Call(instance, sayInstanceMethod);
Action<Child> action = Expression.Lambda<Action<Child>>(mCallInstance, instance).Compile();

action(instanceOne);
action(instanceTwo); // I need to pass in a new instance of Child to this method somehow

Of course this will not output 1, 2 because your _number field is static and after creation of two instances has value 2 for both.

EDIT. If you need to call method with arguments - declare more parameters. For example if SayInstance has one argument of type string, then:

var instanceOne = new Child();
var instanceTwo = new Child();            
var instance = Expression.Parameter(typeof(Child), "instance");
var arg = Expression.Parameter(typeof(string), "arg");
var mCallInstance = Expression.Call(instance, sayInstanceMethod, arg);
Action<Child,string> action = Expression.Lambda<Action<Child,string>>(mCallInstance, instance, arg).Compile();

action(instanceOne, "one");
action(instanceTwo, "two");

Upvotes: 3

Related Questions