更改Action对象实例的参数

时间:2014-03-19 18:52:06

标签: c# .net c#-4.0 .net-4.5

说我有以下代码:

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

是否可以通过仅修改otherValue方法为CallWithParams调用提供参数SeveralCalls的值?

我希望通过SeveralCalls方法为调用注入一个值。

作为一个背景,我正在研究调用tabled paramed存储过程的代码(作为将我的WCF服务集成到遗留代码中的一种方式)。该调用通常与数据库建立自己的连接,但我需要能够在事务中对多个调用进行分组。

如果我这样做,那么我需要每次调用使用相同的SqlConnection对象。 SeveralCalls方法允许我将调用组合在一起,启动事务,并且(希望)将连接传递给实际调用sproc的方法。

4 个答案:

答案 0 :(得分:2)

不,我不相信这是可能的。 () => 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);
}

答案 1 :(得分:2)

您可以使用Expressions执行此操作。您目前正在使用Action的任何地方,请改用Expression<Action>。然后,您可以检查表达式对象,创建一个新对象并使用新表达式而不是初始表达式。这是一个例子。请注意ModifyExpression方法,该方法验证表达式是否为调用CallWithParams方法的lambda。在这个例子中,我正在查看参数,如果第二个缺失或为null,我以编程方式创建一个新的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);
    }

答案 2 :(得分:1)

没有。您Action方法收到的SeveralCalls()个对象不透明。您无法发现他们的调用代码恰好在不使用反射和CIL检查和反汇编的情况下调用CallWithParam()

(这样做会相当复杂,也可能无法保证可移植,因为C#编译器如何将lambda转换为匿名类型的细节不能保证不会改变。换句话说,只有通过假设才有可能C#编译器的非公开实现细节。)

更好的解决方案可能是将CallWithParams()方法放在包含可以更改的字段或属性的类中。然后,您可以根据需要设置此字段/属性,以后对CallWithParams()的任何调用都将相应地执行。这种可行性/健全性可能取决于你真正想要实现的目标。

在我看来,想要为lambdas注入一个参数表明设计有缺陷,所以如果你能分享更多关于你为什么要这样做的细节,也许我们可以帮助找到更好的解决方案。

答案 3 :(得分:1)

不,您的方法接受通用Action。它无法确保它所调用的代码接收一个或两个参数或该参数的类型。

您可以在Action<string> - 方法中接受SeveralCalls,然后在调用中传递该值:

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