本地函数

本地函数有两个常见的用例:公共迭代器方法和公共异步方法。 这两种类型的方法都生成报告错误的时间晚于程序员期望时间的代码。 在迭代器方法中,只有在调用枚举返回的序列的代码时才会观察到任何异常。 在异步方法中,只有当返回的 Task 处于等待状态时才会观察到任何异常。

使用本地函数将参数验证与迭代器实现分离

public static IEnumerable<char> AlphabetSubset3(char start, char end)
{
    if (start < 'a' || start > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if (end < 'a' || end > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");

    return alphabetSubsetImplementation();

    IEnumerable<char> alphabetSubsetImplementation()
    {
        for (var c = start; c < end; c++)
            yield return c;
    }
}

可以对 async 方法采用相同的技术,以确保在异步工作开始之前引发由参数验证引起的异常:

public Task<string> PerformLongRunningWork(string address, int index, string name)
{
    if (string.IsNullOrWhiteSpace(address))
        throw new ArgumentException(message: "An address is required", paramName: nameof(address));
    if (index < 0)
        throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

    return longRunningWorkImplementation();

    async Task<string> longRunningWorkImplementation()
    {
        var interimResult = await FirstWork(address);
        var secondResult = await SecondStep(index, name);
        return $"The results are {interimResult} and {secondResult}. Enjoy.";
    }
}

 

本地函数支持的某些设计也可以使用 lambda 表达式来完成。但是,应该注意,从两者中选用一种的时机和条件其实是存在差别的。

编译器可以执行静态分析,因此本地函数能够在封闭范围内明确分配捕获的变量。

int M()
{
    int y;
    LocalFunction();
    return y;

    void LocalFunction() => y = 0;
}

本地函数可以避免 Lambda 表达式始终需要的堆分配。 如果本地函数永远不会转换为委托,并且本地函数捕获的变量都不会被其他转换为委托的 lambda 或本地函数捕获,则编译器可以避免堆分配。

public Task<string> PerformLongRunningWorkLambda(string address, int index, string name)
{
    if (string.IsNullOrWhiteSpace(address))
        throw new ArgumentException(message: "An address is required", paramName: nameof(address));
    if (index < 0)
        throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

    Func<Task<string>> longRunningWorkImplementation = async () =>
    {
        var interimResult = await FirstWork(address);
        var secondResult = await SecondStep(index, name);
        return $"The results are {interimResult} and {secondResult}. Enjoy.";
    };

    return longRunningWorkImplementation();
}

Lambda 表达式所需的实例化意味着额外的内存分配,后者可能是时间关键代码路径中的性能因素。 本地函数不会产生这种开销。 在以上示例中,本地函数版本具有的分配比 lambda 表达式版本少 2 个。

 

虽然本地函数对 lambda 表达式可能有点冗余,但实际上它们的目的和用法都不一样。 如果想要编写仅从上下文或其他方法中调用的函数,则使用本地函数更高效。

最后,可将本地函数作为迭代器实现,使用 yield return 语法生成一系列值。 Lambda 表达式中不允许使用 yield return 语句。

 

静态本地函数

可以向本地函数添加 static 修饰符,以确保本地函数不会从封闭范围捕获(引用)任何变量。

int M()
{
    int y = 5;
    int x = 7;
    return Add(x, y);

    static int Add(int left, int right) => left + right;
}

 

posted @ 2020-08-04 20:04  yetsen  阅读(224)  评论(0编辑  收藏  举报