修改Action对象实例的参数

本文关键字:参数 实例 对象 Action 修改 | 更新日期: 2023-09-27 17:51:03

假设我有以下代码:

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的方法。

修改Action对象实例的参数

您可以使用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);
}