ฟังก์ชั่นท้องถิ่นกับแลมบ์ดา C # 7.0


178

ฉันกำลังดูการใช้งานใหม่ในC # 7.0และฉันคิดว่ามันน่าสนใจที่พวกเขาได้ใช้ฟังก์ชั่นในท้องถิ่น แต่ฉันไม่สามารถจินตนาการถึงสถานการณ์ที่ฟังก์ชั่นในท้องถิ่นจะได้รับความนิยมมากกว่าการแสดงออกแลมบ์ดา

ฉันเข้าใจว่า lambdas เป็นanonymousฟังก์ชั่นในขณะที่ฟังก์ชั่นในท้องถิ่นไม่ได้ แต่ฉันไม่สามารถเข้าใจสถานการณ์โลกแห่งความเป็นจริงได้ซึ่งฟังก์ชั่นในท้องถิ่นมีข้อได้เปรียบมากกว่าการแสดงออกแลมบ์ดา

ตัวอย่างใด ๆ จะได้รับการชื่นชมมาก ขอบคุณ


9
ข้อมูลทั่วไป, พารามิเตอร์, ฟังก์ชันแบบเรียกซ้ำโดยไม่ต้องเริ่มต้นแลมบ์ดาให้เป็นโมฆะ ฯลฯ
Kirk Woll

5
@KirkWoll - คุณควรโพสต์สิ่งนี้เป็นคำตอบ
Enigmativity

คำตอบ:


276

สิ่งนี้ถูกอธิบายโดย Mads Torgersen ใน C # Design Meeting Notes ที่มีการพูดถึงฟังก์ชั่นในเครื่องครั้งแรก :

คุณต้องการฟังก์ชั่นตัวช่วย คุณใช้มันจากภายในฟังก์ชั่นเดียวเท่านั้นและมีแนวโน้มที่จะใช้ตัวแปรและพารามิเตอร์ประเภทที่อยู่ในขอบเขตในฟังก์ชันที่มี ในทางตรงกันข้ามแลมบ์ดาคุณไม่จำเป็นต้องใช้มันเป็นวัตถุชั้นหนึ่งดังนั้นคุณไม่สนใจที่จะให้มันเป็นประเภทผู้ได้รับมอบหมายและจัดสรรวัตถุผู้แทนจริง นอกจากนี้คุณอาจต้องการให้เรียกซ้ำหรือทั่วไปหรือใช้เป็นตัววนซ้ำ

หากต้องการขยายเพิ่มเติมข้อดีคือ:

  1. ประสิทธิภาพ.

    เมื่อสร้างแลมบ์ดาจะต้องสร้างผู้รับมอบสิทธิ์ซึ่งเป็นการจัดสรรที่ไม่จำเป็นในกรณีนี้ ฟังก์ชั่นในท้องถิ่นเป็นเพียงฟังก์ชั่นจริง ๆ ไม่จำเป็นต้องมีตัวแทน

    นอกจากนี้ฟังก์ชั่นในท้องถิ่นมีประสิทธิภาพมากขึ้นด้วยการจับตัวแปรท้องถิ่น: lambdas มักจะจับตัวแปรเข้าสู่คลาสในขณะที่ฟังก์ชั่นในท้องถิ่นสามารถใช้ struct (ผ่านการใช้ref) ซึ่งหลีกเลี่ยงการจัดสรรอีกครั้ง

    นอกจากนี้ยังหมายถึงการโทรไปยังฟังก์ชั่นในท้องถิ่นนั้นราคาถูกกว่า

  2. ฟังก์ชั่นท้องถิ่นสามารถเรียกซ้ำ

    nullแลมบ์ดาสามารถเรียกซ้ำได้เช่นกัน แต่ก็ต้องใช้รหัสที่น่าอึดอัดใจซึ่งคุณต้องกำหนดให้กับตัวแปรผู้รับมอบสิทธิ์และแลมบ์ดาเป็นครั้งแรก ฟังก์ชั่นในท้องถิ่นสามารถเรียกซ้ำได้ตามธรรมชาติ (รวมถึงการเรียกซ้ำร่วมกัน)

  3. ฟังก์ชั่นท้องถิ่นสามารถทั่วไป

    Lambdas ไม่สามารถเป็นแบบทั่วไปได้เนื่องจากจะต้องกำหนดให้กับตัวแปรที่มีรูปแบบที่เป็นรูปธรรม (ชนิดนั้นสามารถใช้ตัวแปรทั่วไปจากขอบเขตด้านนอก แต่นั่นไม่ใช่สิ่งเดียวกัน)

  4. ฟังก์ชั่นท้องถิ่นสามารถนำมาใช้เป็นตัววนซ้ำ

    Lambdas ไม่สามารถใช้คีย์เวิร์ดyield return(และyield break) เพื่อใช้IEnumerable<T>ฟังก์ชัน -returning ฟังก์ชั่นท้องถิ่นสามารถ

  5. ฟังก์ชั่นท้องถิ่นดูดีขึ้น

    นี่ไม่ได้กล่าวถึงในการอ้างอิงข้างต้นและอาจเป็นเพียงความลำเอียงส่วนตัวของฉัน แต่ฉันคิดว่าไวยากรณ์ของฟังก์ชันปกติดูดีกว่าการกำหนดแลมบ์ดาให้กับตัวแปรผู้แทน ฟังก์ชั่นท้องถิ่นยังมีความกระชับมากขึ้น

    เปรียบเทียบ:

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

22
ฉันต้องการเพิ่มฟังก์ชั่นในท้องถิ่นที่มีชื่อพารามิเตอร์ในด้านผู้โทร แลมบ์ดาไม่
Lensflare

3
@Lensflare มันเป็นความจริงที่ชื่อพารามิเตอร์ของ lambdas ไม่ได้ถูกเก็บรักษาไว้ แต่นั่นเป็นเพราะพวกเขาจะต้องถูกแปลงเป็นผู้รับมอบสิทธิ์ซึ่งมีชื่อของตัวเอง ตัวอย่างเช่นFunc<int, int, int> f = (x, y) => x + y; f(arg1:1, arg2:1);.
svick

1
รายการที่ยอดเยี่ยม! อย่างไรก็ตามฉันสามารถจินตนาการได้ว่าคอมไพเลอร์ IL / JIT สามารถดำเนินการเพิ่มประสิทธิภาพทั้งหมดที่กล่าวถึงใน 1 สำหรับผู้ได้รับมอบหมายหากการใช้งานของพวกเขาปฏิบัติตามกฎบางอย่าง
Marcin Kaczmarek

1
@Casebash เพราะ lambdas objectมักจะใช้ตัวแทนและผู้ร่วมประชุมที่มีการปิดเป็น ดังนั้น lambdas สามารถใช้ struct ได้ แต่มันจะต้องบรรจุอยู่ในกล่องดังนั้นคุณจะยังคงได้รับการจัดสรรเพิ่มเติม
svick

1
@happybits ส่วนใหญ่เมื่อคุณไม่จำเป็นต้องตั้งชื่อให้เช่นเมื่อคุณส่งผ่านไปยังวิธีการ
svick

83

นอกจากนี้ในการตอบที่ดีของ svickมีหนึ่งข้อได้เปรียบมากขึ้นในการฟังก์ชั่นท้องถิ่น:
พวกเขาสามารถกำหนดได้ทุกที่ในฟังก์ชั่นที่แม้หลังจากที่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;
}

5
สิ่งนี้มีประโยชน์จริง ๆ เพราะฉันคุ้นเคยกับการวางฟังก์ชั่นตัวช่วยทั้งหมดไว้#region Helpersที่ด้านล่างของฟังก์ชั่นดังนั้นเพื่อหลีกเลี่ยงความยุ่งเหยิงภายในฟังก์ชันนั้นและหลีกเลี่ยงความยุ่งเหยิงในคลาสหลัก
AustinWBryan

ฉันก็ชอบสิ่งนี้เช่นกัน มันทำให้ฟังก์ชั่นหลักที่คุณกำลังอ่านง่ายขึ้นในขณะที่คุณไม่จำเป็นต้องมองไปรอบ ๆ เพื่อหาที่ที่มันเริ่ม หากคุณต้องการดูรายละเอียดการใช้งานให้มองผ่านจุดสิ้นสุด
Remi Despres-Smyth

3
หากฟังก์ชั่นของคุณใหญ่พวกเขาต้องการพื้นที่ในพวกเขาพวกมันใหญ่เกินไป
เขียน

9

หากคุณสงสัยว่าจะทดสอบฟังก์ชันภายในเครื่องได้อย่างไรคุณควรตรวจสอบ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 เอกสาร

คำปฏิเสธ ผมเป็นหนึ่งในนักพัฒนาที่รับผิดชอบในการJustMock


มันยอดเยี่ยมมากที่เห็นนักพัฒนาที่มีความกระตือรือร้นซึ่งเรียกร้องให้ผู้คนใช้เครื่องมือของพวกเขา คุณสนใจเขียนเครื่องมือของนักพัฒนาเป็นงานเต็มเวลาได้อย่างไร ในฐานะที่เป็นคนอเมริกันความประทับใจของฉันคือการหาอาชีพนั้นยากถ้าคุณไม่มีปริญญาโทหรือปริญญาเอก ใน comp sci
John Zabroski

สวัสดีจอห์นและขอบคุณสำหรับคำพูดที่ใจดี ในฐานะนักพัฒนาซอฟต์แวร์ฉันไม่เห็นอะไรที่ดีไปกว่าการได้รับการชื่นชมจากลูกค้าในเรื่องของมูลค่าที่ฉันจัดหาให้ รวมเข้ากับความปรารถนาสำหรับงานที่ท้าทายและแข่งขันได้และคุณจะได้รับรายการสิ่งที่ฉันต้องการ การเขียนเครื่องมือสำหรับนักพัฒนาผลิตภาพอยู่ในรายการนั้น อย่างน้อยในใจของฉัน :) เกี่ยวกับอาชีพฉันคิดว่า บริษัท ที่ให้บริการเครื่องมือในการพัฒนานั้นค่อนข้างเล็ก ๆ ของ บริษัท ซอฟต์แวร์ทั้งหมดและนี่คือสาเหตุที่ยากที่จะหาโอกาสเช่นนี้
Mihail Vladov

คำถามหนึ่งข้อ ทำไมคุณไม่โทรไปที่ VerifyAll ที่นี่? มีวิธีบอก JustMock ในการตรวจสอบว่ามีการเรียกใช้ฟังก์ชันภายในหรือไม่
John Zabroski

2
สวัสดี @JohnZabroski สถานการณ์ที่ทดสอบไม่จำเป็นต้องเกิดขึ้น แน่นอนคุณสามารถตรวจสอบได้ว่ามีการโทร ก่อนอื่นคุณต้องระบุจำนวนครั้งที่คุณคาดว่าจะเรียกเมธอด เช่นนี้.DoNothing().OccursOnce();และต่อมายืนยันว่าการโทรนั้นเกิดจากการเรียกMock.Assert(foo);เมธอด หากคุณสนใจว่าสถานการณ์อื่น ๆ ได้รับการสนับสนุนคุณสามารถอ่านบทความช่วยเหลือของเราที่เกิดขึ้นได้
Mihail Vladov

0

ฉันใช้ฟังก์ชั่นแบบอินไลน์เพื่อหลีกเลี่ยงความกดดันในการรวบรวมขยะเป็นพิเศษเมื่อต้องรับมือกับวิธีการทำงานที่ยาวนานกว่า สมมติว่าเราต้องการได้รับ 2 ปีหรือข้อมูลการตลาดสำหรับสัญลักษณ์ที่ได้รับ นอกจากนี้ยังสามารถแพ็คฟังก์ชั่นมากมายและตรรกะทางธุรกิจหากจำเป็นต้อง

สิ่งที่หนึ่งคือเปิดการเชื่อมต่อซ็อกเก็ตไปยังเซิร์ฟเวอร์และวนรอบข้อมูลที่ผูกเหตุการณ์กับเหตุการณ์ เราสามารถคิดได้ในลักษณะเดียวกับคลาสที่ได้รับการออกแบบ แต่มีเพียงวิธีเดียวเท่านั้นที่ไม่ได้เขียนวิธีการช่วยเหลือทั่วสถานที่ที่ใช้งานได้จริงกับการทำงานหนึ่งชิ้นเท่านั้น ด้านล่างนี้เป็นตัวอย่างของสิ่งที่อาจมีลักษณะเช่นนี้โปรดทราบว่าฉันใช้ตัวแปรและวิธีการ "ตัวช่วย" อยู่ด้านล่างในที่สุด ในที่สุดฉันก็ลบตัวจัดการเหตุการณ์ออกอย่างชัดเจนถ้าคลาส 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;
   } 
}

คุณสามารถดูข้อดีดังที่กล่าวไว้ด้านล่างที่นี่คุณสามารถดูตัวอย่างการใช้งานได้ หวังว่าจะช่วยอธิบายผลประโยชน์


2
1. นั่นเป็นตัวอย่างที่ซับซ้อนและคำอธิบายเพียงเพื่อแสดงให้เห็นถึงการทำงานในท้องถิ่น 2. ฟังก์ชั่นในท้องถิ่นจะไม่หลีกเลี่ยงการจัดสรรใด ๆ เมื่อเปรียบเทียบกับ lambdas ในตัวอย่างนี้เนื่องจากยังต้องถูกแปลงเป็นผู้รับมอบสิทธิ์ ดังนั้นฉันไม่เห็นว่าพวกเขาจะหลีกเลี่ยง GC อย่างไร
svick

1
ไม่ผ่าน / คัดลอกตัวแปรรอบคำตอบ svick ครอบคลุมส่วนที่เหลือดีจริงๆ ไม่จำเป็นต้องทำซ้ำคำตอบของเขา
Walter Vehoeven
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.