在C#7中使用Local Functions时,如果要将主方法中的参数(或其他局部变量)传递给本地函数,则有两个选项:您可以像任意一样显式声明参数其他功能或者你可以简单地捕捉"来自contains方法的参数/变量并直接使用它们。
一个例子可能说明了这一点:
明确声明
public int MultiplyFoo(int id)
{
return LocalBar(id);
int LocalBar(int number)
{
return number * 2;
}
}
捕获
public int MultiplyFoo(int id)
{
return LocalBar();
int LocalBar()
{
return id * 2;
}
}
两种方法的工作方式相同,但它们调用本地函数的方式不同。
所以我的问题是:
我应该注意两者之间有什么区别吗?我在思考性能,内存分配,垃圾收集,可维护性等方面。
答案 0 :(得分:12)
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;
}
所以不需要堆分配,因为(例如)将lambda表达式转换为委托。另一方面,有结构的构造,然后将值复制到其中。是否通过值传递int
比通过引用传递结构更有效或更低效不太可能是重要的...虽然我猜你有一个巨大的结构作为本地变量,这意味着使用隐式捕获比使用简单的值参数更有效。 (同样,如果您的本地函数使用了大量捕获的局部变量。)
当你有多个局部变量被不同的局部函数捕获时,情况已经变得更加复杂 - 当其中一些局部函数是循环内的局部函数时更是如此。使用ildasm
或Reflector等进行探索可能会非常娱乐性。
一旦你开始做任何复杂的事情,比如写异步方法,迭代器块,本地函数中的lambda表达式,使用方法组转换从本地函数等创建委托......那时我会犹豫继续猜测。您可以尝试以单向方式对代码进行基准测试,也可以查看IL,或者只是编写更简单的代码并依赖于更大的性能验证测试(您已经拥有,对吧?:)让您知道它是否为&#39问题。
答案 1 :(得分: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);
}
当您将id作为参数传递时,将使用传递的id参数调用本地函数。没有什么花哨的,参数存储在方法的堆栈框架上。但是,如果不传递参数,则会使用字段(cDisplayClass10.id = id)创建一个结构(以Daisy指出的名称为“class”),并为其分配id。然后将结构作为引用传递给本地函数。 C#编译器似乎是为了支持闭包。
在性能方面,我使用了Stopwatch.ElapsedTicks,将id作为参数传递得更快。我认为这是因为使用字段创建结构的成本。当我向本地函数添加另一个参数时,性能差距扩大了。
这是我的测试代码,如果有人感兴趣
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}");
}