Mr. Boy
Mr. Boy

Reputation: 63816

Can I make a utility method which takes an arbitrary expression as a parameter and calls it?

The exact reasons I might want to do this are a bit hard to go into without posting loads of code, but here's an example of one scenario:

try
{
    x.Method(123,"test");
    Assert.Fail("No exception thrown");
}
catch (ApplicationException e)
{
    StringAssert.StartsWith(e.Message, "Oh no!");
}

I'd like to refactor this to something like... (pseudo-code)

TestExceptionMessage({x.Method(123,"test")},"Oh no!");

void TestExceptionMessage(func,string message)
{
    try
    {
        func();
        Assert.Fail("No exception thrown");
    }
    catch (ApplicationException e)
    {
        StringAssert.StartsWith(e.Message, message);
    }
}

I know C# is pretty flexible with lambdas and so on, but is this pushing it too far or is it reasonable straightforward?

Upvotes: 4

Views: 67

Answers (1)

p.s.w.g
p.s.w.g

Reputation: 149050

Yes, and it's pretty straight forward. You can pass a Action delegate (or really any delegate type) into a method and call it:

void TestExceptionMessage(Action action, string message)
{
    try
    {
        action();
        Assert.Fail("No exception thrown");
    }
    catch (ApplicationException e)
    {
        StringAssert.StartsWith(e.Message, message);
    }
} 

That delegate can be a reference to a method or it could be a lambda expression like this:

var x = new MyClass();
TestExceptionMessage(() => x.Method(123, "test"),"Oh no!");

However, if you really want to know the expression being used (e.g. if you want to be able to tell what method is being called) you need to use an Expression<Action>

void TestExceptionMessage(Expression<Action> expression, string message)
{
    try
    {
        var methodName = (expression.Body as MethodCallExpression)?.Method.Name;
        if (methodName != null) 
        { 
            Debug.WriteLine($"Calling {methodName}");
        }
        expression.Compile().Invoke();
        Assert.Fail("No exception thrown");
    }
    catch (ApplicationException e)
    {
        StringAssert.StartsWith(e.Message, message);
    }
} 

And thanks to a little compiler magic, you can call with the same lambda syntax:

var x = new MyClass();
TestExceptionMessage(() => x.Method(123, "test"),"Oh no!");

Upvotes: 6

Related Questions