【问题标题】:Local function vs Lambda C# 7.0本地函数与 Lambda C# 7.0
【发布时间】:2017-04-18 00:11:45
【问题描述】:

我正在查看C# 7.0 中的新实现,我发现它们实现了本地函数很有趣,但我无法想象本地函数比 lambda 表达式更受青睐的场景,以及两者之间的区别是什么两个。

我确实知道 lambda 是 anonymous 函数,而本地函数不是,但我无法弄清楚一个真实世界的场景,其中本地函数比 lambda 表达式具有优势

任何示例将不胜感激。谢谢。

【问题讨论】:

  • 泛型、输出参数、递归函数,无需将lambda初始化为null等
  • @KirkWoll - 您应该将此作为答案发布。

标签: c# function lambda c#-7.0


【解决方案1】:

This was explained by Mads Torgersen in C# Design Meeting Notes where local functions were first discussed:

你想要一个辅助函数。您仅在单个函数中使用它,并且它可能使用在该包含函数范围内的变量和类型参数。另一方面,与 lambda 不同的是,您不需要将它作为第一类对象,因此您不需要给它一个委托类型并分配一个实际的委托对象。此外,您可能希望它是递归的或通用的,或者将其实现为迭代器。

进一步扩展,优点是:

  1. 性能。

    创建 lambda 时,必须创建委托,在这种情况下这是不必要的分配。本地函数实际上只是函数,不需要委托。

    此外,局部函数在捕获局部变量方面效率更高:lambdas 通常将变量捕获到一个类中,而局部函数可以使用结构(使用 ref 传递),这再次避免了分配。

    这也意味着调用本地函数更便宜,它们可以内联,可能会进一步提高性能。

  2. 局部函数可以递归。

    Lambdas 也可以是递归的,但它需要笨拙的代码,您首先将 null 分配给委托变量,然后是 lambda。局部函数自然可以递归(包括相互递归)。

  3. 本地函数可以是泛型的。

    Lambda 不能是泛型的,因为它们必须分配给具有具体类型的变量(该类型可以使用外部范围的泛型变量,但这不是一回事)。

  4. 本地函数可以实现为迭代器。

    Lambdas 不能使用yield return(和yield break)关键字来实现@​​987654327@-returning 函数。本地函数可以。

  5. 局部函数看起来更好。

    这在上面的引用中没有提到,可能只是我个人的偏见,但我认为正常的函数语法看起来比将 lambda 分配给委托变量更好。局部函数也更简洁。

    比较:

    int add(int x, int y) => x + y;
    Func<int, int, int> add = (x, y) => x + y;
    

【讨论】:

  • 我想补充一点,本地函数在调用方有参数名称。 Lambda 没有。
  • @Lensflare 确实没有保留 lambda 的参数名称,但那是因为它们必须转换为具有自己名称的委托。例如:Func&lt;int, int, int&gt; f = (x, y) =&gt; x + y; f(arg1:1, arg2:1);.
  • 很棒的清单!但是,我可以想象 IL/JIT 编译器如何执行 1 中提到的所有优化。如果委托的使用符合某些规则,也可以用于委托。
  • @Casebash 因为 lambdas 总是使用一个委托,并且该委托将闭包保存为 object。所以,lambdas 可以使用一个结构,但它必须被装箱,所以你仍然会有额外的分配。
  • @happybits 主要是当你不需要给它命名时,比如当你将它传递给方法时。
【解决方案2】:

除了svick's great answer,本地函数还有一个优势:
它们可以在函数中的任何位置定义,即使在return 语句之后。

public double DoMath(double a, double b)
{
    var resultA = f(a);
    var resultB = f(b);
    return resultA + resultB;

    double f(double x) => 5 * x + 3;
}

【讨论】:

  • 这真的很有用,因为我可以习惯将所有辅助函数放在函数底部的 #region Helpers 中,这样可以避免该函数中的混乱,尤其是避免主函数中的混乱类。
  • 我也很感激。它使您正在查看的主要功能更易于阅读,因为您无需四处寻找它的开始位置。如果您想查看实现细节,请继续往下看。
  • 如果你的函数太大以至于它们需要区域,那么它们就太大了。
【解决方案3】:

如果您还想知道如何测试本地功能,您应该检查JustMock,因为它具有执行此操作的功能。这是一个将被测试的简单类示例:

public class Foo // the class under test
{ 
    public int GetResult() 
    { 
        return 100 + GetLocal(); 
        int GetLocal () 
        { 
            return 42; 
        } 
    } 
}

这是测试的样子:

[TestClass] 
public class MockLocalFunctions 
{ 
    [TestMethod] 
    public void BasicUsage() 
    { 
        //Arrange 
        var foo = Mock.Create<Foo>(Behavior.CallOriginal); 
        Mock.Local.Function.Arrange<int>(foo, "GetResult", "GetLocal").DoNothing(); 

        //Act 
        var result = foo. GetResult(); 

        //Assert 
        Assert.AreEqual(100, result); 
    } 
} 

这里是 JustMock documentation 的链接。

免责声明。我是负责JustMock的开发人员之一。

【讨论】:

    【解决方案4】:

    我使用内联函数来避免垃圾收集压力,特别是在处理运行时间较长的方法时。假设想获得给定股票代码的 2 年或市场数据。此外,如果需要,还可以打包大量功能和业务逻辑。

    我们所做的就是打开一个到服务器的套接字连接,然后循环将一个事件绑定到一个事件的数据。可以将其视为与设计类相同的方式,只有一个不是到处编写真正仅用于一种功能的帮助方法。下面是一些示例,请注意我正在使用变量,并且“帮助”方法在 finally 的下方。在最后我很好地删除了事件处理程序,如果我的 Exchange 类是外部/注入的,我将不会注册任何挂起的事件处理程序

    void List<HistoricalData> RequestData(Ticker ticker, TimeSpan timeout)
    {
        var socket= new Exchange(ticker);
        bool done=false;
        socket.OnData += _onData;
        socket.OnDone += _onDone;
        var request= NextRequestNr();
        var result = new List<HistoricalData>();
        var start= DateTime.Now;
        socket.RequestHistoricalData(requestId:request:days:1);
        try
        {
          while(!done)
          {   //stop when take to long….
            if((DateTime.Now-start)>timeout)
               break;
          }
          return result;
    
        }finally
        {
            socket.OnData-=_onData;
            socket.OnDone-= _onDone;
        }
    
    
       void _OnData(object sender, HistoricalData data)
       {
           _result.Add(data);
       }
       void _onDone(object sender, EndEventArgs args)
       {
          if(args.ReqId==request )
             done=true;
       } 
    }
    

    您可以看到下面提到的优点,这里您可以看到一个示例实现。希望这有助于解释好处。

    【讨论】:

    • 1.这是一个非常复杂的示例和解释,只是为了演示本地功能。 2. 与本例中的 lambdas 相比,局部函数并没有避免任何分配,因为它们仍然必须转换为委托。所以我看不出他们会如何避免 GC。
    • 不传递/复制变量,svick 的回答很好地涵盖了其余部分。无需重复他的答案
    猜你喜欢
    • 2018-03-22
    • 2018-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-02
    相关资源
    最近更新 更多