假设我有以下代码
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,而不必再次设置它。 然后,我希望能够使用不同的参数调用ExecuteMethod(...)。此方法应更新变量_method的参数值,然后调用该方法。 我可以很好地读取参数的数量和它们的值,我只是不确定如何设置这些参数的值?有什么想法吗?
答案 0 :(得分:2)
这不是这样做的方法。现在,您的_method
字段是Func<T>
类型的委托,您希望其正文包含另一个实际执行的方法。你的来电者可以期待很多。我会忘记这种方法并寻找不同的东西。
一种方法是提供一个方法,该方法将对象数组作为其参数(Func<object[], T>
),然后使用适当的参数直接调用它(但从不在其体内使用方法)。对于像C#这样的强类型语言来说,即使这种情况也不常见,因为你失去了所有的类型安全性(但是再一次,你确实希望对你正在设计的这个框架非常灵活。)
另一种方法是获取MethodInfo
实例,然后使用其Invoke
方法。在某种程度上,这甚至可能更好地表达您的意图,因为很明显可执行方法几乎可以实现任何功能。
接下来,您可以使用泛型来获得某种类型的安全性,并要求所有输入参数都包含在单个参数类中。在这种情况下,您可能使用强类型Func<Tparam, Tresult>
方法,并且您的Execute
方法会接受Tparam
实例作为其参数。这将无需任何反思。
<强> [编辑] 强>
正如我写的那样,我会尽量避免反思。既然你写了你基本上需要一个方法结果的缓存,一个简单的方法可能是这样的:
为您的参数列表创建一个包装器,以便您可以“按值”比较它们。我添加了一个示例类,但您甚至可能希望允许显式传递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
}
为单个服务创建缓存。使用上面的包装器类,它应该只检查是否存在缓存结果:
// 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;
}
}
创建将按名称引用服务的最终类:
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 :(得分:1)
就我个人而言,我认为你已经过分了,除非有一个重要的架构需要处理lambda作为表达式树。但是,我离题了。
不要使用反射元素(基本上仅用于表达式树的描述),而是查看MethodCallExpression的Arguments成员。它将包含几个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");
}
只要表达式树可以找到当前实例中MethodCallExpression.method引用的Func,这将有效。
但是,我认为有一种更简单的方式:
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");
}
答案 2 :(得分:1)
不确定为什么这很有用,但这里有:
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"});
}