Chức năng cục bộ so với Lambda C # 7.0


178

Tôi đang xem xét các triển khai mới trong C # 7.0 và tôi thấy thú vị rằng họ đã triển khai các chức năng cục bộ nhưng tôi không thể tưởng tượng được một kịch bản trong đó một chức năng cục bộ sẽ được ưa thích hơn biểu thức lambda và sự khác biệt giữa hai điều này.

Tôi hiểu rằng lambdas là các anonymouschức năng trong khi các chức năng cục bộ thì không, nhưng tôi không thể tìm ra một kịch bản trong thế giới thực, nơi chức năng cục bộ có lợi thế hơn các biểu thức lambda

Bất kỳ ví dụ sẽ được nhiều đánh giá cao. Cảm ơn.


9
Generics, out tham số, hàm đệ quy mà không phải khởi tạo lambda thành null, v.v.
Kirk Woll

5
@KirkWoll - Bạn nên đăng bài này như một câu trả lời.
Enigmativity

Câu trả lời:


276

Điều này đã được giải thích bởi Mads Torgersen trong Ghi chú cuộc họp thiết kế C # nơi các chức năng địa phương được thảo luận lần đầu tiên :

Bạn muốn một chức năng trợ giúp. Bạn chỉ sử dụng nó từ trong một hàm duy nhất và có thể nó sử dụng các biến và loại tham số có trong phạm vi trong hàm chứa đó. Mặt khác, không giống như lambda, bạn không cần nó như một đối tượng hạng nhất, vì vậy bạn không quan tâm đến việc cung cấp cho nó một loại đại biểu và phân bổ một đối tượng ủy nhiệm thực tế. Ngoài ra, bạn có thể muốn nó được đệ quy hoặc chung chung, hoặc thực hiện nó như một trình vòng lặp.

Để mở rộng thêm về nó, những lợi thế là:

  1. Hiệu suất.

    Khi tạo lambda, một đại biểu phải được tạo, đó là sự phân bổ không cần thiết trong trường hợp này. Chức năng địa phương thực sự chỉ là chức năng, không cần đại biểu.

    Ngoài ra, các hàm cục bộ hiệu quả hơn với việc nắm bắt các biến cục bộ: lambdas thường bắt các biến vào một lớp, trong khi các hàm cục bộ có thể sử dụng một cấu trúc (được sử dụng ref), một lần nữa tránh được sự phân bổ.

    Điều này cũng có nghĩa là gọi các chức năng cục bộ là rẻ hơn và chúng có thể được nội tuyến, có thể tăng hiệu suất hơn nữa.

  2. Các chức năng địa phương có thể được đệ quy.

    Lambdas cũng có thể được đệ quy, nhưng nó đòi hỏi mã khó xử, trước tiên bạn gán nullcho một biến đại biểu và sau đó là lambda. Các chức năng cục bộ có thể tự nhiên được đệ quy (bao gồm cả đệ quy lẫn nhau).

  3. Các chức năng địa phương có thể là chung chung.

    Lambdas không thể chung chung, vì chúng phải được gán cho một biến có loại cụ thể (loại đó có thể sử dụng các biến chung từ phạm vi bên ngoài, nhưng đó không phải là điều tương tự).

  4. Các chức năng cục bộ có thể được thực hiện như một trình vòng lặp.

    Lambdas không thể sử dụng từ khóa yield return(và yield break) để thực hiện IEnumerable<T>chức năng quay lại. Chức năng địa phương có thể.

  5. Chức năng địa phương nhìn tốt hơn.

    Điều này không được đề cập trong trích dẫn ở trên và có thể chỉ là thiên kiến ​​cá nhân của tôi, nhưng tôi nghĩ rằng cú pháp hàm bình thường có vẻ tốt hơn so với việc gán lambda cho một biến đại biểu. Chức năng địa phương cũng ngắn gọn hơn.

    Đối chiếu:

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

22
Tôi muốn thêm rằng các hàm cục bộ có tên tham số ở phía người gọi. Lambdas không.
Lensflare

3
@Lensflare Đúng là tên tham số của lambdas không được giữ nguyên, nhưng đó là vì chúng phải được chuyển đổi thành đại biểu, có tên riêng. Ví dụ : Func<int, int, int> f = (x, y) => x + y; f(arg1:1, arg2:1);.
Svick

1
Danh sách tuyệt vời! Tuy nhiên, tôi có thể tưởng tượng làm thế nào trình biên dịch IL / JIT có thể thực hiện tất cả các tối ưu hóa được đề cập trong 1. cũng cho các đại biểu nếu việc sử dụng của họ tuân thủ các quy tắc nhất định.
Marcin Kaczmarek

1
@Casebash Bởi vì lambdas luôn sử dụng một đại biểu và đại biểu đó giữ việc đóng cửa như một object. Vì vậy, lambdas có thể sử dụng một cấu trúc, nhưng nó sẽ phải được đóng hộp, vì vậy bạn vẫn sẽ có phân bổ bổ sung đó.
Svick

1
@happybits Hầu hết khi bạn không cần đặt tên cho nó, giống như khi bạn chuyển nó sang phương thức.
Svick

83

Ngoài câu trả lời tuyệt vời của Svick, còn có một lợi thế nữa cho các hàm cục bộ:
Chúng có thể được định nghĩa ở bất cứ đâu trong hàm, ngay cả sau returncâu lệnh.

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;
}

5
Điều này thực sự hữu ích, vì tôi có thể quen với việc đặt tất cả các hàm của trình trợ giúp #region Helpersở dưới cùng của hàm, vì vậy để tránh sự lộn xộn trong hàm đó và đặc biệt tránh sự lộn xộn trong lớp chính.
AustinWBryan

Tôi cũng đánh giá cao điều này. Nó làm cho chức năng chính mà bạn đang nhìn dễ đọc hơn, vì bạn không cần phải nhìn xung quanh để tìm nơi nó bắt đầu. Nếu bạn muốn xem chi tiết thực hiện, hãy tiếp tục nhìn về cuối.
Remi Despres-Smyth

3
nếu các chức năng của bạn quá lớn, họ cần các vùng trong đó, thì chúng quá lớn.
thợ rèn

9

Nếu bạn cũng tự hỏi làm thế nào để kiểm tra chức năng cục bộ, bạn nên kiểm tra JustMock vì nó có chức năng để làm điều đó. Dưới đây là một ví dụ lớp đơn giản sẽ được kiểm tra:

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

Và đây là cách kiểm tra:

[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); 
    } 
} 

Đây là một liên kết đến tài liệu JustMock .

Khước từ. Tôi là một trong những nhà phát triển chịu trách nhiệm cho JustMock .


thật tuyệt khi thấy các nhà phát triển đam mê như vậy ủng hộ mọi người sử dụng công cụ của họ. Làm thế nào bạn có hứng thú với việc viết các công cụ dành cho nhà phát triển như một công việc toàn thời gian? Là một người Mỹ, ấn tượng của tôi là khó có thể tìm được những nghề nghiệp như vậy trừ khi bạn có bằng thạc sĩ hoặc tiến sĩ. trong comp sci.
John Zabroski

Xin chào John và cảm ơn bạn vì những lời tốt đẹp. Là một nhà phát triển phần mềm, tôi không thấy gì tốt hơn là được khách hàng đánh giá cao về giá trị tôi cung cấp cho họ. Kết hợp điều đó với mong muốn cho công việc đầy thách thức và cạnh tranh và bạn sẽ nhận được một danh sách khá hạn chế về những điều tôi sẽ đam mê. Viết công cụ phát triển năng suất là trong danh sách đó. Ít nhất là trong suy nghĩ của tôi :) Về sự nghiệp, tôi nghĩ rằng các công ty cung cấp công cụ phát triển chiếm tỷ lệ khá nhỏ trong tất cả các công ty phần mềm và đây là lý do tại sao khó tìm được cơ hội như vậy.
Mihail Vladov

Một câu hỏi riêng biệt. Tại sao bạn không gọi ConfirmAll tại đây? Có cách nào để bảo JustMock xác minh chức năng cục bộ đã được gọi không?
John Zabroski

2
Xin chào @JohnZabroski, kịch bản được thử nghiệm không yêu cầu sự cố xảy ra. Tất nhiên, bạn có thể xác minh rằng một cuộc gọi đã được thực hiện. Trước tiên, bạn cần xác định số lần bạn mong đợi phương thức được gọi. Như thế này: .DoNothing().OccursOnce();Và sau đó khẳng định rằng cuộc gọi được thực hiện bằng cách gọi Mock.Assert(foo);phương thức. Nếu bạn quan tâm đến việc các kịch bản khác được hỗ trợ như thế nào, bạn có thể đọc bài viết trợ giúp Xác nhận Sự xuất hiện của chúng tôi .
Mihail Vladov

0

Tôi sử dụng các hàm nội tuyến để tránh áp lực thu gom rác đặc biệt khi xử lý các phương thức chạy dài hơn. Giả sử một người muốn có được 2 năm hoặc dữ liệu thị trường cho một biểu tượng đánh dấu nhất định. Ngoài ra, người ta có thể đóng gói rất nhiều chức năng và logic kinh doanh nếu cần.

những gì người ta làm là mở một kết nối ổ cắm đến máy chủ và lặp qua dữ liệu liên kết một sự kiện với một sự kiện. Người ta có thể nghĩ về nó giống như một lớp được thiết kế, chỉ có một người không viết các phương thức trợ giúp ở khắp mọi nơi thực sự chỉ hoạt động cho một pice chức năng. dưới đây là một số ví dụ về cách nó có thể trông như thế nào, xin lưu ý rằng tôi đang sử dụng các biến và các phương thức "trợ giúp" ở dưới cuối cùng. Cuối cùng, tôi loại bỏ các trình xử lý sự kiện một cách độc đáo, nếu lớp Exchange của tôi sẽ ở bên ngoài / được tiêm, tôi sẽ không có bất kỳ trình xử lý sự kiện đang chờ xử lý nào được đăng ký

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;
   } 
}

Bạn có thể thấy những lợi thế như được đề cập dưới đây, ở đây bạn có thể thấy một triển khai mẫu. Hy vọng rằng sẽ giúp giải thích những lợi ích.


2
1. Đó là một ví dụ thực sự phức tạp và giải thích chỉ để chứng minh các chức năng địa phương. 2. Các hàm cục bộ không tránh bất kỳ sự phân bổ nào khi so sánh với lambdas trong ví dụ này, bởi vì chúng vẫn phải được chuyển đổi thành các đại biểu. Vì vậy, tôi không thấy làm thế nào họ sẽ tránh được GC.
Svick

1
không chuyển / sao chép các biến xung quanh, câu trả lời của Svick bao gồm phần còn lại thực sự tốt. Không cần phải lặp lại câu trả lời của mình
Walter Vehoeven
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.