Установка значений параметров анонимной функции в C # - PullRequest
3 голосов
/ 12 октября 2011

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

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 ]

2 голосов
/ 12 октября 2011

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

Один из способов - предоставить метод, который принимает массив объектов в качестве параметра (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);
1 голос
/ 12 октября 2011

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

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"});
    }
1 голос
/ 12 октября 2011

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

Вместо того, чтобы работать с отражающими элементами (которые в основном предназначены только для описания в терминах дерева выражений), посмотрите на член 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");
}
...