Установка значений параметров анонимной функции в C#

Допустим, у меня есть следующий код

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"}););
}

В приведенном выше коде я хочу установить частную переменную для некоторого Func, который указывает на функцию anonymoust и никогда не должен устанавливать ее снова. Затем я хотел бы иметь возможность вызывать ExecuteMethod(...) с другими параметрами. Этот метод должен обновить значения параметров переменной _method, а затем вызвать метод. Я могу прочитать количество параметров и их значения в порядке, я просто не уверен, как установить значения для этих параметров? Есть мысли по этому поводу?

3 ответа

Решение

Это не способ сделать это. Прямо сейчас, ваш _method поле является делегатом типа Func<T>и вы ожидаете, что его тело содержит еще один метод, который фактически выполняется. Этого можно ожидать от ваших абонентов. Я бы забыла об этом подходе и искала бы что-то другое.

Одним из способов является предоставление метода, который принимает массив объектов в качестве параметра (Func<object[], T>), а затем вызывать его напрямую с соответствующими параметрами (но никогда не вызывать метод в его теле). Даже это не так часто встречается для строго типизированного языка, такого как C#, поскольку вы теряете безопасность всех типов (но опять же, вы действительно хотите быть достаточно гибкими с этой средой, которую вы разрабатываете).

Другим способом было бы получить MethodInfo экземпляр, а затем использовать его Invoke метод. В некотором смысле, это может даже лучше выразить ваши намерения, потому что будет очевидно, что исполняемый метод способен практически на все.

Затем вы можете использовать обобщенные элементы для обеспечения некоторой безопасности типов и требовать, чтобы все ваши входные параметры были заключены в один класс параметров. В этом случае вы можете иметь строго типизированный Func<Tparam, Tresult> метод, и ваш Execute метод будет принимать Tparam экземпляр как его параметр. Это избавит от необходимости каких-либо размышлений.

[Редактировать]

Как я уже писал, я постараюсь избежать размышлений. Поскольку вы написали, вам в основном нужен кэш результатов метода, простой подход может быть что-то вроде:

  1. Создайте обертку для вашего списка параметров, чтобы вы могли сравнивать их "по значению". Я добавил пример класса, но вы можете даже разрешить передачу IEqualityComparer явно, так что вам не нужно переопределять Equals для каждого частичного параметра.

    // 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. Создайте кеш для одного сервиса. Используя описанный выше класс-оболочку, он должен просто проверить, существует ли кэшированный результат:

    // 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. Создайте последний класс, который будет ссылаться на сервисы по имени:

    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);
        }
    }
    

Ваша настройка кэша будет выглядеть следующим образом:

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

И вы бы просто назвали это так:

var result = serviceCache.GetResult("ServiceA", paramA, paramB, paramC);

Лично я думаю, что вы ПУТИТЕ за борт, если только нет переопределенной архитектуры, которая должна работать с лямбда-выражением в качестве дерева выражений. Но я отвлекся.

Вместо того чтобы работать с отражающими элементами (которые в основном предназначены только для описания в терминах дерева выражений), посмотрите на член Arguments вашего MethodCallExpression. Он будет содержать несколько объектов ContantExpression, которые вы можете заменить своими собственными ConstantExpressions, содержащими строковые значения, которые вы хотите передать. Тем не менее, выражения доступны только для чтения; Вы должны восстановить эквивалентное дерево для этого вызова.

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");
}

Это будет работать до тех пор, пока дерево выражений сможет найти Func, на который ссылается MethodCallExpression.method в текущем экземпляре.

Тем не менее, я думаю, что есть гораздо более простой способ:

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");
}

Не уверен, почему это полезно, но здесь идет:

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();
    }
}

Назовите это так:

    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"});
    }
Другие вопросы по тегам