3

Lets say i have the following code

private Func<T> _method;

public void SetExecutableMethod<T>(Func<T> methodParam)
{
    _method = methodParam;
}

public T ExecuteMethod(object[] parameterValues)
{
    //get the number of parameters _method has;
    var methodCallExpression = _method.Body as MethodCallExpression;
    var method = methodCallExpression.Method;
    ParameterInfo[] methodParams = method.GetParameters();

    //So i now have a list of parameters for the method call,
    //How can i update the parameter values for each of these?
    for (int i = 0; i < parameters.Count(); i++ )
    {
        methodParams[i] = ???''
    }

    return _method.Compile()();
}

public void InitAndTest()
{
    SetExecutableMethod( () => _service.SomeMethod1("param1 placeholder", "param2 placeholder") );

    T result1 = ExecuteMethod(new object[]{"Test1", "Test2"});
    T result2 = ExecuteMethod(new object[]{"Test3", "Test4"}););
}

In the above code, i want to set a private variable to some Func that points to an anonymoust function and never have to set it again. I then would like to be able to call ExecuteMethod(...) with different parameters. This method should update the parameter values of the variable _method and then invoke the method. I can read the number of parameters and their values fine, i just am not sure how to set the values for those parameter? Any thoughts on this?

mike01010
  • 5,226
  • 6
  • 44
  • 77
  • Could you explain what the final goal is? How are you planning to use an object which can accept an arbitrary number of parameters, and process them in an arbitrary way? How will the calling code know what to pass to this object? There might be a better way to do it altogether if your intention was a bit clearer. – vgru Oct 11 '11 at 21:23
  • i am implementing a generic cache handler for service calls. the cache key will be a hashcode of the method itself + parameters. the value will be the results of the method call. this basically allows me to implement a class that does not know or care about the things being passed into a method, only needs to execute method and cache results them for later retrieval. – mike01010 Oct 11 '11 at 22:08
  • I have updated my answer with a basic example. Keep in mind that a hashcode cannot be the *actual* key. Your cache (dictionary) may calculate a hash code to speed up searching, but you should ensure that each parameter implements an `IEqualityComparer` or overrides its `Equals` method, and then compare each of them. – vgru Oct 12 '11 at 08:44

3 Answers3

2

This is not the way to do it. Right now, your _method field is a delegate of type Func<T>, and you expect that its body contains yet another method which is actually executed. That is a lot to expect from your callers. I would forget about this approach and look for something different.

One way would be to supply a method which takes an array of objects as its parameter (Func<object[], T>), and then invoke it directly with appropriate parameters (but never a method in its body). Even this is less common for a strongly typed language like C# since you lose all type safety (but then again, you do want to be pretty flexible with this framework you are designing).

The other way would be to get a MethodInfo instance, and then use its Invoke method. In a way, this might even express your intents better, because it will be obvious that the executable method is capable of virtually anything.

Next, you could use generics to get some type safety, and require that all your input parameters are wrapped inside a single parameter class. In that case, you might have a strongly typed Func<Tparam, Tresult> method, and your Execute method would accept a Tparam instance as its parameter. This would void the need for any reflection.

[Edit]

As I wrote, I would try to avoid reflection. Since you wrote you basically need a cache of method results, a simple approach might be something like:

  1. Create a wrapper for your list of parameters so that you can compare them "by value". I added an example class, but you might even want to allow passing an IEqualityComparer explicitly, so that you don't have to override Equals for each partial parameter.

    // implements `IEquatable` for a list of parameters
    class Parameters : IEquatable<Parameters>
    {
        private readonly object[] _parameters;
        public Parameters(object[] parms)
        {
            _parameters = parms;
        }
    
        #region IEquatable<Parameters> Members
    
        public bool Equals(Parameters other)
        {
            if (other == null)
                return false;
    
            if (_parameters.Length != other._parameters.Length)
                return false;
    
            // check each parameter to see if it's equal
            // ...     
        }
    
        public override bool Equals(object obj)
        {
            return Equals(obj as Parameters);
        }
    
        public override int GetHashCode()
        { ... }
    
        #endregion
    }
    
  2. Create a cache for a single service. Using the wrapper class above, it should simply check if a cached result exists:

    // contains cached results for a single service
    class CachedCallInfo
    {
        private readonly Func<object[], object> _method;
        private readonly Dictionary<Parameters, object> _cache
            = new Dictionary<Parameters, object>();
    
        public CachedCallInfo(Func<object[], object> method)
        {
            _method = method;
        }
    
        public T GetResult<T>(params object[] parameters)
        {
            // use out Parameters class to ensure comparison
            // by value
            var key = new Parameters(parameters);
            object result = null;
    
            // result exists?
            if (!_cache.TryGetValue(key, out result))
            {
                // do the actual service call
                result = _method(parameters);
    
                // add to cache
                _cache.Add(key, result);
            }
            return (T)result;
        }
    }
    
  3. Create the final class which will reference services by name:

    public class ServiceCache
    {
        private readonly Dictionary<string, CachedCallInfo> _services =
            new Dictionary<string, CachedCallInfo>();
    
        public void RegisterService(string name, Func<object[], object> method)
        {
            _services[name] = new CachedCallInfo(method);
        }
    
        // "params" keyword is used to simplify method calls
        public T GetResult<T>(string serviceName, params object[] parameters)
        {
            return _services[serviceName].GetResult<T>(parameters);
        }
    }
    

Your cache setup will then look like this:

serviceCache.RegisterService("ServiceA", @params => DoSomething(@params));
serviceCache.RegisterService("ServiceB", @params => SomethingElse(@params));

And you would simply call it like this:

var result = serviceCache.GetResult("ServiceA", paramA, paramB, paramC);
vgru
  • 49,838
  • 16
  • 120
  • 201
  • Thanks groo, all the suggestions here were good, but yours have got me thinking about slightly different approach. The only issue i have with defining the method as Func is that this requires that the service calls i want to cache match such a signature. That is not going to always be possible as i may be caching service calls not 'owned' by me. this is my an Func that points to an anonymous delegate works i think – mike01010 Oct 12 '11 at 13:34
1

Personally, I think you're going WAY overboard, unless there is an overriding architecture need to deal with the lambda as an expression tree. But, I digress.

Instead of working with the reflective elements (which are basically for description only in terms of an expression tree), look at the Arguments member of your MethodCallExpression. It will contain several ContantExpression objects, which you can replace with your own ConstantExpressions containing the string values you want to pass in. However, Expressions are read-only; you have to rebuild an equivalent tree for this call.

public class FuncManipulator<T>
{
    private Func<T> _method;

    public void SetExecutableMethod(Func<T> methodParam)
    {
        _method = methodParam;
    }

    //you forgot the "params" keyword
    public T ExecuteMethod(params object[] parameterValues)
    {
        //get the number of parameters _method has;
        var methodCallExpression = _method.Body as MethodCallExpression;
        var arguments = methodCallExpression.Arguments;

        var newArguments = new List<Expression>();        
        for (int i = 0; i < arguments.Count(); i++ )
        {
            newArguments.Add(Expression.Constant(parameterValues[i]));
        }

        //"Clone" the expression, specifying the new parameters instead of the old.
        var newMethodExpression = Expression.Call(methodCallExpression.Object, 
                                                  methodCallExpression.Method, 
                                                  newArguments)

        return newMethodExpression.Compile()();
    }

}

...

public void InitAndTest()
{
    SetExecutableMethod( () => _service.SomeMethod1("param1 placeholder", "param2 placeholder") );

    T result1 = ExecuteMethod("Test1", "Test2");
    T result2 = ExecuteMethod("Test3", "Test4");
    T result3 = ExecuteMethod("Test6", "Test5");
}

This will work as long as the expression tree can find the Func referred to by MethodCallExpression.method within the current instance.

However, I think there's a much simpler way:

public class FuncManipulator<T>
{
    private Func<T> _method;

    public void SetExecutableMethod(Func<T> methodParam)
    {
        _method = methodParam;
    }

    //you must pass the actual array; we are creating a closure reference that will live
    //as long as the delegate
    public void SetMethodParams(object[] param)
    {
        _param = param;
    } 

    public T ExecuteMethod(params object[] passedParam)
    {
       //We have to re-initialize _param based on passedParam
       //instead of simply reassigning the reference, because the lambda
       //requires we don't change the reference.
       for(int i=0; i<_param.Length; i++)
          _param[i] = passedParam.Length <= i ? null : passedParam[i];

       //notice we don't pass _param; the lambda already knows about it
       //via the reference set up when declaring the lambda.
       return _method(); 
    }

}

...

public void InitAndTest()
{
    //this is an "external closure" we must keep in memory
    object[] param = new object[2];
    SetExecutableMethod( () => _service.SomeMethod1(param[0], param[1]) );
    //We do so by passing the reference to our object
    SetMethodParams(param);

    //now, don't ever reassign the entire array.
    //the ExecuteMethod function will replace indices without redefining the array.
    T result1 = ExecuteMethod("Test1", "Test2");
    T result2 = ExecuteMethod("Test3", "Test4");
    T result3 = ExecuteMethod("Test6", "Test5");
}
KeithS
  • 70,210
  • 21
  • 112
  • 164
  • 1
    believe me, i'm of your opinion. but the powers that be want more abstraction and to reduce redundancy. i will give it to them and they will see that with great abstraction, comes great unreadability. :) – mike01010 Oct 11 '11 at 21:05
  • Could you get the MethodInfo of the method that you want to point at and then use its invoke method with your array of objects? – Joey Oct 11 '11 at 21:09
  • @Keith, Arguements property appear to be a ReadOnly collection – mike01010 Oct 11 '11 at 21:10
  • @mike: Yep. You have to use the Expression.Call() method to "clone" the expression you have, using the existing Object and Method but new Arguments. Check the edit. – KeithS Oct 11 '11 at 21:35
  • @KeithS: Nice solution. Only side note, your implementation is not thread safe. Not sure if that's an issue with OP.... – BFree Oct 11 '11 at 21:43
1

Not sure why this is useful, but here goes:

public class SomeCrazyClass<T>
{
    private Expression<Func<T>> _method;

    public void SetExecutableMethod(Expression<Func<T>> methodParam)
    {
        _method = methodParam;
    }

    public object ExecuteMethod(SomeService someService, object[] parameterValues)
    {
        var methodCallExpression = _method.Body as MethodCallExpression;
        var method = methodCallExpression.Method;
        var methodCall = Expression.Call(Expression.Constant(someService), method,
                                parameterValues.Select(Expression.Constant));

        return Expression.Lambda(methodCall).Compile().DynamicInvoke();
    }
}

Call it like so:

    public static void InitAndTest()
    {
        var something = new SomeCrazyClass<int>(); //or whatever type your method returns
        var _service = new SomeService();
        something.SetExecutableMethod(() => _service.SomeMethod1("param1 placeholder", "param2 placeholder"));

        var result1 = something.ExecuteMethod(_service,new object[] {"Test1", "Test2"});
        var result2 = something.ExecuteMethod(_service, new object[] {"Test3", "Test4"});
    }
BFree
  • 102,548
  • 21
  • 159
  • 201
  • Instead of passing in _service as a parameter to ExecuteMethod, is there a way to determine the class instance of that method? for example if i look at MemberExpression.Object, i can tell that it refers to the class instance, but for some reason i am not able to use it as it too returns an expression and i'm not able to cast it to an actual instance of the servie. – mike01010 Oct 11 '11 at 22:47