将函数传递给Func<>隐式地在方法调用中

本文关键字:方法 调用 函数 Func | 更新日期: 2023-09-27 17:50:54

假设我有以下两个方法:

    public void ConsumesDeletage (Func<object> consumer) { ... }
    public object DoesSomething() { ... }

在同一个类中,我有以下第三个方法:

    public void ForceDelegateConsumption()
    {
        ConsumesDeletage(DoesSomething);
    }

每次我调用ForceDelegateConsumption(),内部会发生什么?我是否为每次调用都创建了一个新的Func对象?在JITer完成了这段代码之后;它是否变成了一个简单的函数指针,只需要计算一次?完全不同的东西?

将函数传递给Func<>隐式地在方法调用中

每次我调用ForceDelegateConsumption(),内部会发生什么?我是否为每次调用都创建了一个新的Func对象?

是的,但这往往是一个相当便宜的操作:您只是将"方法组"转换为Func<>。它可能比你正在考虑的任何替代方案都快或更快。

下面是一个用来演示的基准。时间是如此之快,以至于我包含了一个no-op,以便我们可以捕获在基准测试开销上花费了多少时间:
void Main()
{
    // Enter setup code here
    Func<object> cachedDoesSomething = DoesSomething;
    var actions = new[]
    {
        new TimedAction("No-op", () => 
        {
        }),
        new TimedAction("method call", () => 
        {
            DoesSomething();
        }),
        new TimedAction("ForceDelegateConsumption", () => 
        {
            ForceDelegateConsumption();
        }),
        new TimedAction("ForceDelegateConsumptionInline", () => 
        {
            ConsumesDelegate(DoesSomething);
        }),
        new TimedAction("DoesSomethingInlined", () => 
        {
            ConsumesDelegate(() => null);
        }),
        new TimedAction("CachedLambda", () => 
        {
            ConsumesDelegate(cachedDoesSomething);
        }),
        new TimedAction("Explicit lambda", () => 
        {
            ConsumesDelegate(() => DoesSomething());
        }),   
    };
    const int TimesToRun = 10000000; // Tweak this as necessary
    TimeActions(TimesToRun, actions);
}
public void ConsumesDelegate (Func<object> consumer) { 
    consumer();
}
public object DoesSomething() { return null; }
public void ForceDelegateConsumption()
{
    ConsumesDelegate(DoesSomething);
}

结果:

Message                        DryRun1 DryRun2 FullRun1 FullRun2 
No-op                          0.046   0.0004   56.5705  57.3681 
method call                    0.1294  0.0004   96.169   98.9377 
ForceDelegateConsumption       0.2555  0.0004  315.6183 284.0828 
ForceDelegateConsumptionInline 0.1997  0.0012  278.4389 263.8278 
DoesSomethingInlined           0.2909  0.0008  145.8749 152.2732 
CachedLambda                   0.1388  0.0004  125.7794 135.8126 
Explicit lambda                0.2444  0.0004  308.683  304.2111 

所有的"FullRun"数字都是运行方法 1000万次所需的毫秒数。

所以你可以看到我的基准框架花费50毫秒,即使我们什么都不做。简单地调用DoSomething方法,假设它什么都不做,运行1000万次需要50毫秒。然后,它需要额外的200毫秒或更少的时间来调用ConsumesDelegate,将DoSomething作为一个方法组传递给它(同样是1000万次)。

如果您正在编写将在性能关键路径上被调用数百万次的代码,您将需要考虑完全避免lambdas。它们增加了一点点内存开销,以及一点点额外的CPU时间。否则,我就不会避免方法-组转换了。

从头开始重建它并不是特别昂贵,真的,你只是创建了一个有两个字段的新对象,一个对象引用和一个MethodInfo实例(或类似的东西)。

尝试自己缓存这样的委托几乎是不值得的,因为这是一个非常快速的操作。