C#中的本地函数 - 在传递参数时捕获或不捕获?

Dan*_*plo 26 c# function c#-7.0

在C#7中使用本地函数时,如果要将主方法中的参数(或其他局部变量)传递给本地函数,则有两个选项:您可以像任何其他函数一样显式声明参数,也可以简单地从包含方法"捕获"参数/变量并直接使用它们.

一个例子可能说明了这一点:

明确声明

public int MultiplyFoo(int id)
{
    return LocalBar(id);

    int LocalBar(int number)
    {
        return number * 2;
    }
}
Run Code Online (Sandbox Code Playgroud)

捕获

public int MultiplyFoo(int id)
{
    return LocalBar();

    int LocalBar()
    {
        return id * 2;
    }
}
Run Code Online (Sandbox Code Playgroud)

两种方法的工作方式相同,但它们调用本地函数的方式不同.

所以我的问题是:

我应该注意两者之间有什么区别吗?我在考虑性能,内存分配,垃圾收集,可维护性等方面.

Jon*_*eet 17

C#中的本地函数在捕获方面非常聪明 - 至少在Roslyn实现中是这样.当编译器能够保证您不是从本地函数创建委托(或者做其他会延长变量生命周期的东西)时,它可以使用一个ref参数,将生成的结构中的所有捕获变量与之通信本地功能.例如,你的第二种方法最终会像:

public int MultiplyFoo(int id)
{
    __MultiplyFoo__Variables variables = new __MultiplyFoo__Variables();
    variables.id = id;
    return __Generated__LocalBar(ref variables);
}

private struct __MultiplyFoo__Variables
{
    public int id;
}

private int __Generated__LocalBar(ref __MultiplyFoo__Variables variables)
{
    return variables.id * 2;
}
Run Code Online (Sandbox Code Playgroud)

所以不需要堆分配,因为(例如)将lambda表达式转换为委托.另一方面,有结构的构造,然后将值复制到其中.传递一个intby值是否比通过引用传递struct更有效或更低效率不太可能有意义......虽然我猜想在你有一个巨大的struct作为局部变量的情况下,这意味着使用隐式捕获会更多比使用简单的值参数更有效.(同样,如果您的本地函数使用了大量捕获的局部变量.)

当你有多个局部变量被不同的局部函数捕获时,情况已经变得更加复杂 - 当其中一些局部函数是循环内的局部函数时更是如此.使用ildasm或反射器等进行探索可能非常有趣.

一旦你开始做任何复杂的事情,比如编写异步方法,迭代器块,本地函数中的lambda表达式,使用方法组转换从本地函数等创建委托......那时我会犹豫继续猜测.您可以尝试以各种方式对代码进行基准测试,或者查看IL,或者只是编写更简单的代码并依赖于更大的性能验证测试(您已经拥有,对吧?:)来告诉您是否存在问题.


And*_*ndy 5

这是一个有趣的问题.首先,我已经反编译了构建输出.

public int MultiplyFoo(int id)
{
  return LocalFunctionTests.\u003CMultiplyFoo\u003Eg__LocalBar\u007C0_0(id);
}

public int MultiplyBar(int id)
{
  LocalFunctionTests.\u003C\u003Ec__DisplayClass1_0 cDisplayClass10;
  cDisplayClass10.id = id;
  return LocalFunctionTests.\u003CMultiplyBar\u003Eg__LocalBar\u007C1_0(ref cDisplayClass10);
}
Run Code Online (Sandbox Code Playgroud)

当您将id作为参数传递时,将使用传递的id参数调用本地函数.没有什么花哨的,参数存储在方法的堆栈框架上.但是,如果不传递参数,则会使用字段(cDisplayClass10.id = id)创建一个结构(以Daisy指出的名称为"class"),并为其分配id.然后将结构作为引用传递给本地函数.C#编译器似乎是为了支持闭包.

在性能方面,我使用了Stopwatch.ElapsedTicks,传递id作为参数始终更快.我认为这是因为使用字段创建结构的成本.当我向本地函数添加另一个参数时,性能差距扩大了.

  • 传递身份证号码:2247
  • 没有通过Id:2566

这是我的测试代码,如果有人感兴趣的话

public int MultiplyFoo(int id, int id2)
{
    return LocalBar(id, id2);

    int LocalBar(int number, int number2)
    {
        return number * number2 * 2;
    }
}

public int MultiplyBar(int id, int id2)
{
    return LocalBar();

    int LocalBar()
    {
        return id * id2 * 2;
    }
}


[Fact]
public void By_Passing_Id()
{
    var sut = new LocalFunctions();

    var watch = Stopwatch.StartNew();
    for (int i = 0; i < 10000; i++)
    {
        sut.MultiplyFoo(i, i);
    }

    _output.WriteLine($"Elapsed: {watch.ElapsedTicks}");
}

[Fact]
public void By_Not_Passing_Id()
{
    var sut = new LocalFunctions();

    var watch = Stopwatch.StartNew();
    for (int i = 0; i < 10000; i++)
    {
        sut.MultiplyBar(i, i);
    }

    _output.WriteLine($"Elapsed: {watch.ElapsedTicks}");
}
Run Code Online (Sandbox Code Playgroud)