假设我有以下代码:
void Main()
{
SeveralCalls(() => CallWithParams("test"),
() => CallWithParams("AnotherTest"));
}
public void SeveralCalls(params Action[] methodsToCall)
{
foreach (var methodToCall in methodsToCall)
{
methodToCall();
}
}
public void CallWithParams(string someValue, string otherValue = null)
{
Console.WriteLine("SomeValue: " + someValue);
Console.WriteLine("OtherValue: " + otherValue);
}
是否可以通过仅修改SeveralCalls
方法来为CallWithParams
的调用提供参数otherValue
的值?
我想在调用中注入一个值,如果它是通过SeveralCalls
方法来的。
作为一点背景,我正在编写调用表参数化存储过程的代码(作为将WCF服务集成到遗留代码中的一种方式)。调用通常建立自己到数据库的连接,但我需要能够在事务中对多个调用进行分组。
如果我这样做,那么我需要每个调用使用相同的SqlConnection对象。SeveralCalls
方法将允许我将调用分组在一起,启动一个事务,并(希望)将连接传递给将实际调用sproc的方法。
您可以使用Expression
s完成此操作。在你目前使用Action
的任何地方,使用Expression<Action>
代替。然后,您可以检查表达式对象,创建一个新的表达式,并使用新的表达式代替初始表达式。这里有一个例子。注意ModifyExpression
方法,它验证表达式是调用CallWithParams
方法的lambda。在这个例子中,我正在查看参数,如果第二个参数缺失或为空,我以编程方式创建一个新的lambda表达式,第二个参数等于"覆盖的值"。注意,我必须将null
值添加到CallWithParams
lambda中。显然,你不能使用带有默认参数的表达式,所以我只能在lambdas中给它一个默认值。
static void Main()
{
SeveralCalls(() => CallWithParams("test", null),
() => CallWithParams("AnotherTest", null));
}
public static void SeveralCalls(params Expression<Action>[] expressions)
{
foreach (var expression in expressions)
{
var modifiedExpression = ModifyExpression(expression);
var action = modifiedExpression.Compile();
action();
}
}
private static Expression<Action> ModifyExpression(Expression<Action> expression)
{
var lambda = expression as LambdaExpression;
if (lambda == null)
return expression;
var call = lambda.Body as MethodCallExpression;
if (call == null)
return expression;
var method = typeof(Program).GetMethod("CallWithParams");
if (call.Method != method)
return expression;
if (call.Arguments.Count < 1 || call.Arguments.Count > 2)
return expression;
var firstArgument = call.Arguments[0];
var secondArgument = (call.Arguments.Count == 2 ? call.Arguments[1] : null);
var secondArgumentAsConstant = secondArgument as ConstantExpression;
if (secondArgumentAsConstant == null || secondArgumentAsConstant.Value == null)
secondArgument = Expression.Constant("overridden value");
var modifiedCall = Expression.Call(method, firstArgument, secondArgument);
var modifiedLambda = Expression.Lambda<Action>(modifiedCall);
return modifiedLambda;
}
public static void CallWithParams(string someValue, string otherValue = null)
{
Console.WriteLine("SomeValue: " + someValue);
Console.WriteLine("OtherValue: " + otherValue);
}
不,我不相信这是可能的。() => CallWithParams("test")
被编译为调用CallWithParams("test", null)
的代码,这两个值都是硬编码的。在SeveralCalls
中没有办法(除了潜在的一些复杂的反射和/或IL发射)修改这一点。
如果你可以修改Main
,这可能是一个很好的方法:
void Main()
{
SeveralCalls("Some other string",
otherValue => CallWithParams("test", otherValue),
otherValue => CallWithParams("AnotherTest", otherValue));
}
public void SeveralCalls(string otherValue, params Action<string>[] methodsToCall)
{
foreach (var methodToCall in methodsToCall)
{
methodToCall(otherValue);
}
}
public void CallWithParams(string someValue, string otherValue = null)
{
Console.WriteLine("SomeValue: " + someValue);
Console.WriteLine("OtherValue: " + otherValue);
}
或:
string otherValue = null;
void Main()
{
SeveralCalls(() => CallWithParams("test", this.otherValue),
() => CallWithParams("AnotherTest", this.otherValue));
}
public void SeveralCalls(params Action[] methodsToCall)
{
this.otherValue = "Some other string";
foreach (var methodToCall in methodsToCall)
{
methodToCall();
}
}
// added static just to clarify that the otherValue here is separate from the
// one in 'this'
public static void CallWithParams(string someValue, string otherValue = null)
{
Console.WriteLine("SomeValue: " + someValue);
Console.WriteLine("OtherValue: " + otherValue);
}
No。SeveralCalls()
方法接收的Action
对象是不透明的。如果不使用反射和CIL检查和反汇编,就无法发现它们的调用代码恰好调用了CallWithParam()
。
(这样做会相当复杂,而且也可能不能保证可移植,因为c#编译器如何将lambdas转换为匿名类型的细节不能保证不改变。换句话说,只有假设c#编译器的非公开实现细节才有可能。
一个更好的解决方案可能是将CallWithParams()
方法放在一个包含可以更改的字段或属性的类中。然后,您可以根据需要设置此字段/属性,并且将来对CallWithParams()
的任何调用都将相应地执行。这样做的可行性/合理性可能取决于你真正想要完成的是什么。
在我看来,想要在lambdas中注入参数是一个有缺陷的设计,所以如果你能分享更多关于为什么你想这样做的细节,也许我们可以帮助找到一个更好的解决方案。
不,您的方法接受通用的Action
。它无法确定它所调用的代码接收到一个或两个参数,也无法确定该参数是什么类型。
您可以在SeveralCalls
-方法中接受Action<string>
,然后在调用中传递该值:
void Main()
{
SeveralCalls(extra => CallWithParams("test", extra),
extra => CallWithParams("AnotherTest", extra));
}
public void SeveralCalls(params Action<string>[] methodsToCall)
{
foreach (var methodToCall in methodsToCall)
{
methodToCall("some other param");
}
}
public void CallWithParams(string someValue, string otherValue = null)
{
Console.WriteLine("SomeValue: " + someValue);
Console.WriteLine("OtherValue: " + otherValue);
}