คำสั่งไวยากรณ์ผลตอบแทนแปลก


106

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

ดังนั้นฉันได้ดูโค้ด MoreLINQ แล้วฉันก็สังเกตเห็นวิธีนี้

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));

    return _(); IEnumerable<TSource> _()
    {
        var knownKeys = new HashSet<TKey>(comparer);
        foreach (var element in source)
        {
            if (knownKeys.Add(keySelector(element)))
                yield return element;
        }
    }
}

คำสั่งผลตอบแทนแปลก ๆ นี้คืออะไร? return _();เหรอ?


6
หรือคุณหมายถึง: return _(); IEnumerable<TSource> _()?
Alex K.

6
@ สตีฟฉันสงสัยว่า OP อ้างถึงreturn _(); IEnumerable<TSource> _()มากกว่าyield return?
Rob

5
ฉันคิดว่าเขาหมายถึงแนวreturn _(); IEnumerable<TSource> _()นี้ เขาอาจสับสนในลักษณะที่ดูเหมือนไม่ใช่คำสั่งส่งคืนจริง
Mateusz

5
@AkashKava OP กล่าวว่ามีคำสั่งกลับมาแปลก ๆ น่าเสียดายที่รหัสนี้มีคำสั่งส่งคืนสองรายการ ดังนั้นจึงเป็นเรื่องที่เข้าใจได้หากผู้คนสับสนว่าเขา / เธอหมายถึงอะไร
mjwills

5
แก้ไขคำถามและขออภัยในความสับสนอีกครั้ง
kuskmen

คำตอบ:


106

นี่คือ C # 7.0 ซึ่งรองรับฟังก์ชันภายในเครื่อง ....

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
       this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
    {
        if (source == null) throw new 
           ArgumentNullException(nameof(source));
        if (keySelector == null) throw 
             new ArgumentNullException(nameof(keySelector));

        // This is basically executing _LocalFunction()
        return _LocalFunction(); 

        // This is a new inline method, 
        // return within this is only within scope of
        // this method
        IEnumerable<TSource> _LocalFunction()
        {
            var knownKeys = new HashSet<TKey>(comparer);
            foreach (var element in source)
            {
                if (knownKeys.Add(keySelector(element)))
                    yield return element;
            }
        }
    }

C # ปัจจุบันกับ Func<T>

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
       this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
    {
        if (source == null) throw new 
           ArgumentNullException(nameof(source));
        if (keySelector == null) throw 
             new ArgumentNullException(nameof(keySelector));

        Func<IEnumerable<TSource>> func = () => {
            var knownKeys = new HashSet<TKey>(comparer);
            foreach (var element in source)
            {
                if (knownKeys.Add(keySelector(element)))
                    yield return element;
            }
       };

        // This is basically executing func
        return func(); 

    }

เคล็ดลับคือ _ () ถูกประกาศหลังจากใช้งานซึ่งก็ใช้ได้ดี

การใช้ฟังก์ชันท้องถิ่นอย่างเหมาะสม

ตัวอย่างด้านบนเป็นเพียงการสาธิตวิธีการใช้วิธีการอินไลน์ แต่ส่วนใหญ่แล้วถ้าคุณจะเรียกใช้วิธีการเพียงครั้งเดียวก็จะไม่มีประโยชน์

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

หลายครั้งที่เรามีโค้ดซ้ำใน method ลองดูตัวอย่างนี้ ..

  public void ValidateCustomer(Customer customer){

      if( string.IsNullOrEmpty( customer.FirstName )){
           string error = "Firstname cannot be empty";
           customer.ValidationErrors.Add(error);
           ErrorLogger.Log(error);
           throw new ValidationError(error);
      }

      if( string.IsNullOrEmpty( customer.LastName )){
           string error = "Lastname cannot be empty";
           customer.ValidationErrors.Add(error);
           ErrorLogger.Log(error);
           throw new ValidationError(error);
      }

      ... on  and on... 
  }

ฉันสามารถเพิ่มประสิทธิภาพนี้ด้วย ...

  public void ValidateCustomer(Customer customer){

      void _validate(string value, string error){
           if(!string.IsNullOrWhitespace(value)){

              // i can easily reference customer here
              customer.ValidationErrors.Add(error);

              ErrorLogger.Log(error);
              throw new ValidationError(error);                   
           }
      }

      _validate(customer.FirstName, "Firstname cannot be empty");
      _validate(customer.LastName, "Lastname cannot be empty");
      ... on  and on... 
  }

4
@ZoharPeled ดี .. รหัสโพสต์ไม่แสดงการใช้งานสำหรับฟังก์ชั่นที่ .. :)
ร็อบ

2
@ColinM ข้อดีอย่างหนึ่งคือฟังก์ชันนิรนามสามารถเข้าถึงตัวแปรจาก 'โฮสต์' ได้อย่างง่ายดาย
mjwills

6
แน่ใจหรือว่าใน C # -speak นี้เรียกว่า anonymous function? ดูเหมือนว่าจะมีชื่อคือ_AnonymousFunctionหรือเพียง_ในขณะที่ฉันคาดหวังว่าฟังก์ชันที่ไม่ระบุตัวตนของแท้จะเป็นอย่าง(x,y) => x+yนั้น ฉันจะเรียกสิ่งนี้ว่าฟังก์ชันท้องถิ่น แต่ฉันไม่คุ้นเคยกับคำศัพท์ C #
ไค

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

6
@ColinM ตัวอย่าง kuksmen ที่โพสต์เป็นหนึ่งในเหตุผลหลักที่นำมาใช้ในที่สุด - เมื่อคุณสร้างฟังก์ชันด้วยจะyield returnไม่มีการเรียกใช้โค้ดจนกว่าจะมีการแจกแจงจริง นี่เป็นสิ่งที่ไม่พึงปรารถนาเนื่องจากคุณต้องการยืนยันข้อโต้แย้งทันที วิธีเดียวที่จะทำได้ใน C # คือการแยกวิธีออกเป็นสองวิธี - วิธีหนึ่งกับyield returns และอีกวิธีหนึ่งไม่มี วิธีการแบบอินไลน์ช่วยให้คุณสามารถประกาศyieldวิธีการใช้ภายในหลีกเลี่ยงความยุ่งเหยิงและการใช้วิธีที่อาจเกิดขึ้นในทางที่ผิดซึ่งเป็นวิธีการภายในของผู้ปกครองอย่างเคร่งครัดและไม่สามารถใช้ซ้ำได้
Luaan

24

ลองพิจารณาตัวอย่างที่ง่ายกว่านี้

void Main()
{
    Console.WriteLine(Foo()); // Prints 5
}

public static int Foo()
{
    return _();

    // declare the body of _()
    int _()
    {
        return 5;
    }
}

_() เป็นฟังก์ชันท้องถิ่นที่ประกาศภายในเมธอดที่มีคำสั่ง return


3
ใช่ฉันรู้เกี่ยวกับฟังก์ชั่นท้องถิ่นมันเป็นการจัดรูปแบบที่หลอกฉัน ... หวังว่านี่จะไม่กลายเป็นมาตรฐาน
kuskmen

20
คุณหมายถึงการประกาศฟังก์ชันที่เริ่มต้นในบรรทัดเดียวกันหรือไม่? ถ้าเป็นเช่นนั้นฉันยอมรับว่ามันแย่มาก!
Stuart

3
ใช่นั่นคือสิ่งที่ฉันหมายถึง
kuskmen

9
ยกเว้นว่าการตั้งชื่อมันขีดล่างก็น่ากลัวเช่นกัน
Icepickle

1
@AkashKava: คำถามไม่ได้อยู่ที่ว่า C # ถูกกฎหมายหรือไม่ แต่รหัสนั้นเข้าใจง่ายหรือไม่ (และด้วยเหตุนี้จึงง่ายต่อการดูแลรักษาและน่าอ่าน) เมื่อจัดรูปแบบเช่นนี้ ความชอบส่วนตัวมีบทบาท แต่ฉันมักจะเห็นด้วยกับสจวร์ต
PJTraill
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.