如何防止动作参数成为异步lambda?

时间:2017-01-11 10:21:32

标签: c# lambda async-await

我正在编写一个小包装器(MyWrapper),用于单元测试。它的目的是用try-catch包装测试代码,以便捕获一个特定的异常(MySpecialException),然后忽略测试。

为什么我这样做不应与此问题相关。

根据以下代码,如何阻止其他人传递Action并使用async这样? 或换句话说:如何强制他们使用MyWrapper.ExecuteAsync(Func<Task>)

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

2 个答案:

答案 0 :(得分:2)

我担心你在编译时无法真正阻止它,但你可以编写另一个重载,在这种情况下会被提取,告诉他们应该使用ExecuteAsync代替: / p>

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

答案 1 :(得分:1)

正如其他答案所述,我认为你不能在编译时阻止它。但是,您可以执行hacky解决方法并抛出异常。灵感来自this answer。它可能不是一个好的解决方案,但它至少可以使测试失败。

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)
    {

    }
}

测试:

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