简体   繁体   中英

How to prevent an action parameter from being an async lambda?

I am writing a small wrapper ( MyWrapper ) for use in unit tests. Its purpose is to wrap test code with a try-catch in order to catch one specific exception ( MySpecialException ) and then ignore the test.

Why I do that should not be relevant for this question.

Given the code below , how do I prevent others from passing an Action and using async like this? Or in other words: How do I force them to use MyWrapper.ExecuteAsync(Func<Task>) instead?


using System;
using System.Threading.Tasks;
using NUnit.Framework;

namespace PreventAsyncActionLambdaExample
{
    [TestFixture]
    public class Example
    {
        [Test]
        public async Task ExampleTest()
        {
            // How do I prevent others from passing an action and using async like this?
            // Or in other words: How do I force them to use MyWrapper.ExecuteAsync(Func<Task>) instead?
            MyWrapper.Execute(async () =>
            {
                var cut = new ClassUnderTest();

                await cut.DoSomethingAsync();

                Assert.Fail("Problem: This line will never be reached");
            });
        }
    }

    public static class MyWrapper
    {
        // This method SHOULD NOT, BUT WILL be used in this example
        public static void Execute(Action action)
        {
            try
            {
                action();
            }
            catch (MySpecialException)
            {
            Assert.Ignore("Ignored due to MySpecialException");
            }
        }

        // This method SHOULD BE USED in this example, BUT WILL NOT be used.
        public static async Task ExecuteAsync(Func<Task> func)
        {
            try
            {
                await func();
            }
            catch (MySpecialException)
            {
                Assert.Ignore("Ignored due to MySpecialException");
            }
        }
    }

    public class MySpecialException : Exception
    {
        // This is another exception in reality which is not relevant for this example
    }

    public class ClassUnderTest
    {
        public Task DoSomethingAsync()
        {
            return Task.Delay(20); // Simulate some work
        }
    }
}

I am afraid that you can't really prevent this at compile-time but you could write another overload that will be picked-up in this case to tell them that they are supposed to use ExecuteAsync instead:

public static Task Execute(Func<Task> action)
{
    throw new Exception("Please use the ExecuteAsync(Func<Task> func) method instead if you will be passing async lambdas");
}

As mentioned in other answers, I do not think you can prevent it in compile time. However, you can do a hacky workaround and throw an exception. Inspired by this answer . It might not be a good solution, but it could at least make the test fail.

public static bool IsThisAsync(Action action)
{
    return action.Method.IsDefined(typeof(AsyncStateMachineAttribute),
        false);
}

// This method SHOULD NOT, BUT WILL be used in this example
public static void Execute(Action action)
{
    try
    {
        if (IsThisAsync(action))
        {
            Console.WriteLine("Is async");
            throw new ArgumentException("Action cannot be async.", nameof(action));
        }
        else
        {
            Console.WriteLine("Is not async");
        }

        action();
    }
    catch (MySpecialException)
    {

    }
}

And tests:

[TestClass]
public class MyWrapperTests
{
    // Will not pass
    [TestMethod]
    public void ShouldAllowAsyncAction()
    {
        // This will throw an exception
        MyWrapper.Execute(async () =>
        {
            Assert.IsTrue(true);
            await Task.Run(() =>
            {
                Console.WriteLine("Kind of async");
            });
        });
    }

    // Will pass, since ArgumentException is expected.
    [TestMethod]
    [ExpectedException(typeof(ArgumentException))]
    public void ShouldThrowArgumentExceptionWhenAsync()
    {
        // This will throw an exception. But that's expected.
        MyWrapper.Execute(async () =>
        {
            Assert.IsTrue(true);
            await Task.Run(() =>
            {
                Console.WriteLine("Kind of async");
            });
        });
    }

    // Passes
    [TestMethod]
    public void ShouldAllowSyncAction()
    {
        MyWrapper.Execute(() =>
        {
            Assert.IsTrue(true);
        });
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM