减少方法到委托的隐式转换
避免频繁直接将方法当作参数直接传入,因为每次调用时都会产生一个委托实例,应该将方法缓存起来:
private Func<object> func;
private int a;
[SetUp]
public void SetUp()
{
func = Func;
}
[Test]
public void MemberMethodAllocationTest()
{
Assert.That(() =>
{
//有 Allocated
//编译后:FuncInvoke(new Func<object>(Func));
FuncInvoke(Func);
}, Is.AllocatingGCMemory());
}
[Test]
public void VariableAllocationTest()
{
Assert.That(() =>
{
//无 GC
FuncInvoke(func);
}, Is.AllocatingGCMemory());
}
private object Func() => null;
private object FuncInvoke(Func<object> func) => func.Invoke();
减少函数闭包
Lambda 表达式的调用,MSBuild 编译器会分三种情况处理:
- 若捕获到类成员(包括成员方法),则生成一个本成员的方法,并创建一个该方法的委托实例;
- 若捕获到方法内的局部变量,则生成一个内部类和内部类的一个成员方法,并创建该内部类和委托的实例;
- 若没有捕获任何变量,则生成一个内部类和内部类的一个静态方法,并创建该内部类,如果是第一次调用,则 new 一个委托并缓存;
我们要避免的是第一种和第二种情况:
public void BadMethod_1()
{
int a = 5;
// 捕获局部变量,会 new 一个内部类的实例对象和一个委托对象
Func<int> func = () => a;
func.Invoke()
}
int b = 5;
public void BadMethod_2()
{
// 捕获成员变量,会 new 一个本类的一个委托对象
Func<int> func = () => b;
func.Invoke()
}
只要是任何涉及到闭包调用的函数,都会在函数开头插入一个创建内部类的逻辑,也就是说,就算你没有调用到真正的闭包代码,也是会产生 GC Allocated:
public class TestClass
{
public void GCTest()
{
var obj = new object();
// 本次调用会产生 GC堆 的 Allocated
Test(true, obj);
}
public void Test(bool isReturn,object source)
{
if(isReturn)
return;
// 捕获函数参数
Select(item => Get(source));
}
public void Select(Func<object, object> func) { }
public object Get(object obj) => null;
}
上面的代码编译后:
public class TestClass
{
//编译器生成的内部类
[CompilerGenerated]
private sealed class <>c__DisplayClass1_0
{
[System.Runtime.CompilerServices.Nullable(0)]
public TestClass <>4__this;
[System.Runtime.CompilerServices.Nullable(0)]
public object source;
internal object <Test>b__0(object item)
{
return <>4__this.Get(source);
}
}
public void GCTest()
{
object obj = new object();
Test(true, obj);
}
public void Test(bool isReturn, object source)
{
//无论如何它都会创建一个内部类实例,即使你直接 Return
<>c__DisplayClass1_0 <>c__DisplayClass1_ = new <>c__DisplayClass1_0();
<>c__DisplayClass1_.<>4__this = this;
<>c__DisplayClass1_.source = source;
if (!isReturn)
{
Select(new Func<object, object>(<>c__DisplayClass1_.<Test>b__0));
}
}
public void Select(Func<object, object> func) {}
public object Get(object obj) => null;
}