Mark Gibbons
Mark Gibbons

Reputation: 581

Compile a LambdaExpression with additional parameter

I am trying to get a MethodInfo object and call Invoke() on it. Here's what I've tried so far:

public class ContactCustomFieldsFacet
{
    public Dictionary<string, string> Fields { get; set; } = new Dictionary<string, string>();

}

public static class FacetExtensions
{
    public static Func<ContactCustomFieldsFacet, object> Compile(string body)
    {
        ParameterExpression prm = Expression.Parameter(typeof(ContactCustomFieldsFacet), typeof(ContactCustomFieldsFacet).Name);
        LambdaExpression exp = DynamicExpressionParser.ParseLambda(new[] { prm }, typeof(object), body);
        return (Func<ContactCustomFieldsFacet, object>)exp.Compile();
    }
}

var lambda = FacetExtensions.Compile($"ContactCustomFieldsFacet.Fields[\"test\"]");
var propertyMethod = lambda.Method;
// No control over the below code - it's called by a third party library
ContactCustomFieldsFacet facet = GetFacet();
propertyMethod.Invoke(null, new [] { facet }); // System.ArgumentException: MethodInfo must be a runtime MethodInfo object.

Essentially I want to be able to dynamically create an expression that has the ContactCustomFieldsFacet.Fields dictionary index field hardcoded for that instance. I realise I might be on the wrong track so any pointers would be appreciated.

.NET Fiddle: https://dotnetfiddle.net/ecMRye

EDIT

Thanks to Corey I've updated the code but am still getting a similar issue

.NET Fiddle: https://dotnetfiddle.net/5hDOsd

Upvotes: 1

Views: 998

Answers (1)

Corey
Corey

Reputation: 16574

Locating the method you want to call is reasonably simple reflection. The hard part is figuring out the name of the method to call. Sometimes the simplest thing to do is to create an expression and have a look at how the Compiler put it together.

For instance, this:

Expression<Func<Dictionary<string, string>, string>> expr = d => d["test"];

If you inspect (or Dump(), if you're using LINQPad) the expr variable you'll be able to wander through the generated expression tree and look at things like the method information in the Call node.

In your case it seems that you want to invoke get_Item on the Dictionary<string, string> class. It requires an object reference and a string parameter for the key. Object instance will be the Fields property value from the supplied ContactCustomFieldsFacet so we'll need to grab that.

Building the whole thing by hand might look something like this:

public static Func<ContactCustomFieldsFacet, string> MakeAccessor(string key)
{
    var tFacet = typeof(ContactCustomFieldsFacet);
    var piFields = tFacet.GetProperty("Fields");
    var miFieldsGet = piFields.PropertyType.GetMethod("get_Item");

    var param = Expression.Parameter(tFacet, "facet");
    var lambda = Expression.Lambda<Func<ContactCustomFieldsFacet, object>>
    (
        Expression.Call
        (
            // Instance for invocation: param.Fields
            Expression.MakeMemberAccess(param, piFields),
            // Method to call
            miFieldsGet,
            // Call arguments
            Expresion.Constant(key)
        ),
        param
    );

    return lambda.Compile();
}

Invoking that method will get you a Func<> that returns the value for a specific key, as set at the time the Func<> was built. For instance:

var fnGetTest = MakeAccessor("test");
var value = fnGetTest(someFacetInstance);

This all assumes that the dictionary stores reference types - string works fine. If it's a value type (int for example) you'll need to box the value by Converting to object.

Upvotes: 2

Related Questions