将简单的 lambda 表达式或局部函数分配给委托的性能

Performance of assigning a simple lambda expression or a local function to a delegate

当使用一个非常简单的表达式作为键来创建带有 Enumerable.ToLookup<TSource, TKey> Method (IEnumerable<TSource>, Func<TSource, TKey>) 的 ILookup 时,我可以使用 lambda 表达式:

var lk = myItems.ToLookup((x) => x.Name);

或局部函数:

var lk = myItems.ToLookup(ByName);

string ByName(MyClass x)
{
     return x.Name;
}

我很好奇这个简单的案例是否有区别。

to SO user svick 中给出了一个很好的论据,为什么一般来说局部函数比 lambda 更可取。

重要的一点是性能上的差异:

When creating a lambda, a delegate has to be created, which is an unnecessary allocation in this case. Local functions are really just functions, no delegates are necessary.

但是由于我们将它传递给 ToLookup(),所以无论如何都会创建一个委托。性能上还有区别吗?
我可以想象编译器必须为每次调用 myItems.ToLookup 创建一个新的委托 lambda,而本地方法只需要一个委托实例;这是真的吗?

svick's 中的第二个性能差异点是捕获变量和创建闭包:

Also, local functions are more efficient with capturing local variables: lambdas usually capture variables into a class, while local functions can use a struct (passed using ref), which again avoids an allocation.

但是,由于表达式不使用外部作用域中的变量,因此不必像 stated by Reed Copsey and expanded by Eric Lippert in answer to Are Lambda expressions in C# closures?:

A lambda may be implemented using a closure, but it is not itself necessarily a closure. — Reed Copsey
[...]
a function that can be treated as an object is just a delegate. What makes a lambda a closure is that it captures its outer variables. — Eric Lippert

这有点矛盾Eric Lippert himself is his to Eric Lippert 将局部函数解释为命名的 lambda:

A local function is basically just a lambda with an associated name.

但这是在较少的技术细节级别,对于 lambda's/local 函数的委托, 捕获外部范围变量。

这个简单的表达式不是递归的,不是通用的,也不是迭代器。至于哪个更好看,见仁见智。
那么,简单的不捕获、非递归、非泛型和非迭代器 lambda 表达式与局部函数之间在性能(或其他方面)上是否存在任何差异?

使用当前版本的编译器 (Roslyn 2.8.0),使用 lambda 的版本效率更高,因为它缓存了委托。

查看 the IL of code that has your two samples in separate methods,实际上是:

sealed class HelperClass
{
    public static readonly HelperClass Instance = new HelperClass();

    public static Func<MyClass, string> CachedDelegate;

    internal string LambdaByName(MyClass x)
    {
        return x.Name;
    }

    internal string LocalFunctionByName(MyClass x)
    {
        return x.Name;
    }
}

void Lambda(IEnumerable<MyClass> myItems)
{
    var lk = myItems.ToLookup(HelperClass.CachedDelegate ??
        (HelperClass.CachedDelegate =
            new Func<MyClass, string>(HelperClass.Instance.LambdaByName)));
}

void LocalFunction(IEnumerable<MyClass> myItems)
{
    var lk = myItems.ToLookup(
        new Func<MyClass, string>(HelperClass.Instance.LocalFunctionByName)));
}

注意 Lambda 如何仅分配一次委托并在之后使用缓存的委托,而 LocalFunction 每次都分配委托。这使得 Lambda 在这种特定情况下更有效率。

虽然有a proposal on GitHub to change the compiler to make LocalFunction as efficient as Lambda.