ทำไม Func <T, bool> แทน Predicate <T>


210

นี่เป็นเพียงคำถามที่อยากรู้อยากเห็นฉันสงสัยว่าใครมีคำตอบที่ดีในการ:

ใน. NET Framework Class Library เรามีตัวอย่างสองวิธีนี้:

public static IQueryable<TSource> Where<TSource>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, bool>> predicate
)

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate
)

ทำไมพวกเขาใช้Func<TSource, bool>แทนPredicate<TSource>? ดูเหมือนว่าPredicate<TSource>จะถูกใช้โดยList<T>และArray<T>ในขณะที่Func<TSource, bool>มีการใช้งานโดยวิธีการทั้งหมดQueryableและEnumerableวิธีการและส่วนขยาย ... มันขึ้นอยู่กับที่?


21
เอ่อใช่การใช้งานที่ไม่สอดคล้องกันของไดรฟ์เหล่านี้ฉันก็บ้าเหมือนกัน
George Mauer

คำตอบ:


170

ในขณะที่Predicateได้รับการแนะนำในเวลาเดียวกันList<T>และArray<T>ใน. net 2.0 ความแตกต่างFuncและความActionผันแปรมาจาก. net 3.5

เพรดิเคตเหล่านั้นFuncส่วนใหญ่จะใช้เพื่อความมั่นคงในตัวดำเนินการ LINQ ในฐานะของสุทธิ 3.5, เกี่ยวกับการใช้Func<T>และรัฐแนวทาง :Action<T>

อย่าใช้ประเภท LINQ ใหม่Func<>และ Expression<>แทนผู้ได้รับมอบหมายที่กำหนดเองและภาค


7
เย็นไม่เคยเห็น guildelines เหล่านั้นก่อน =)
Svish

6
จะยอมรับสิ่งนี้เป็นคำตอบเนื่องจากมีคะแนนเสียงมากที่สุด และเนื่องจาก Jon Skeet มีตัวแทนจำนวนมาก ... : p
Svish

4
นั่นเป็นแนวทางที่แปลกประหลาดตามที่เขียนไว้ แน่นอนว่าควรระบุว่า "ใช้ประเภท LINQ ใหม่" Func <> "และ" Action <> "[... ]" Expression <> เป็นสิ่งที่แตกต่างอย่างสิ้นเชิง
Jon Skeet

3
ที่จริงแล้วคุณต้องวางไว้ในบริบทของแนวทางซึ่งเกี่ยวกับการเขียนผู้ให้บริการ LINQ ดังนั้นใช่สำหรับผู้ประกอบการ LINQ แนวทางคือการใช้ Func <> และ Expression <> เป็นพารามิเตอร์สำหรับวิธีการขยาย ฉันเห็นด้วยฉันขอขอบคุณแนวทางที่แยกต่างหากเกี่ยวกับการใช้ Func และการกระทำ
Jb Evain

ฉันคิดว่าประเด็นของ Skeet คือ Expression <> ไม่ใช่แนวทาง มันเป็นคุณสมบัติคอมไพเลอร์ C # ในการจดจำประเภทนั้นและทำสิ่งที่แตกต่างกับมัน
Daniel Earwicker

116

ฉันเคยสงสัยเรื่องนี้มาก่อน ฉันชอบPredicate<T>ผู้แทน - มันดีและเป็นคำอธิบาย อย่างไรก็ตามคุณต้องพิจารณาถึงสิ่งต่อไปนี้Where:

Where<T>(IEnumerable<T>, Func<T, bool>)
Where<T>(IEnumerable<T>, Func<T, int, bool>)

ที่ช่วยให้คุณสามารถกรองตามดัชนีของรายการได้เช่นกัน เป็นสิ่งที่ดีและสอดคล้องกันในขณะที่:

Where<T>(IEnumerable<T>, Predicate<T>)
Where<T>(IEnumerable<T>, Func<T, int, bool>)

จะไม่เป็น


11
คำกริยา <int, bool> จะค่อนข้างน่าเกลียด - คำกริยามักจะเป็น (IME ของวิทยาการคอมพิวเตอร์) ที่แสดงไว้ในค่าเดียว มันอาจจะเป็นคำกริยา <คู่ <T, int >> แน่นอน แต่ที่แม้ไม่สวยงามเท่า :)
จอนสกีต

จริงเหรอ ... ฮิฮิ ไม่ฉันเดาว่า Func นั้นสะอาดกว่า
Svish

2
ยอมรับคำตอบอื่น ๆ เนื่องจากแนวทาง หวังว่าฉันจะทำเครื่องหมายมากกว่าหนึ่งคำตอบว่า! คงจะดีถ้าฉันสามารถทำเครื่องหมายจำนวนของคำตอบว่า "สำคัญมาก" หรือ "นอกจากนี้ยังมีจุดที่ดีที่ควรจะตั้งข้อสังเกตนอกเหนือไปจากคำตอบ"
Svish

6
อืมเพิ่งเห็นว่าฉันเขียนความคิดเห็นครั้งแรกผิด: P คำแนะนำของฉันแน่นอนว่าจะเป็นเพรดิเคต <T, int> ...
Svish

4
@ JonSkeet ฉันรู้ว่าคำถามนี้แก่แล้ว แต่รู้ว่าสิ่งที่น่าขันคืออะไร? ชื่อของพารามิเตอร์ในการวิธีขยายเป็นWhere predicateเหอ = พี
Conrad Clark

32

แน่นอนเหตุผลที่แท้จริงสำหรับการใช้Funcแทนผู้แทนเฉพาะคือ C # ปฏิบัติต่อผู้ประกาศแยกต่างหากเป็นประเภทที่แตกต่างกันโดยสิ้นเชิง

แม้ว่าFunc<int, bool>และPredicate<int>ทั้งคู่จะมีอาร์กิวเมนต์และชนิดส่งคืนเหมือนกัน แต่ก็ไม่เข้ากันได้กับการมอบหมาย ดังนั้นหากทุกไลบรารีประกาศประเภทผู้รับมอบสิทธิ์ของตนเองสำหรับแต่ละรูปแบบของผู้รับมอบสิทธิ์ห้องสมุดเหล่านั้นจะไม่สามารถทำงานร่วมกันได้เว้นแต่ผู้ใช้จะแทรกผู้แทน "bridging" เพื่อทำการแปลง

    // declare two delegate types, completely identical but different names:
    public delegate void ExceptionHandler1(Exception x);
    public delegate void ExceptionHandler2(Exception x);

    // a method that is compatible with either of them:
    public static void MyExceptionHandler(Exception x)
    {
        Console.WriteLine(x.Message);
    }

    static void Main(string[] args)
    {
        // can assign any method having the right pattern
        ExceptionHandler1 x1 = MyExceptionHandler; 

        // and yet cannot assign a delegate with identical declaration!
        ExceptionHandler2 x2 = x1; // error at compile time
    }

ด้วยการสนับสนุนให้ทุกคนใช้ Func Microsoft หวังว่าจะช่วยบรรเทาปัญหาประเภทตัวแทนที่เข้ากันไม่ได้ ผู้ได้รับมอบหมายของทุกคนจะเล่นกันอย่างดีเพราะพวกเขาจะถูกจับคู่ตามพารามิเตอร์ / ประเภทผลตอบแทน

ไม่สามารถแก้ปัญหาทั้งหมดได้เนื่องจากFunc(และAction) ไม่มีoutหรือrefพารามิเตอร์ แต่ใช้น้อยกว่าปกติ

ปรับปรุง:ในความคิดเห็น Svish พูดว่า:

แต่ถึงกระนั้นการเปลี่ยนประเภทพารามิเตอร์จาก Func เป็น Predicate และย้อนกลับดูเหมือนจะไม่สร้างความแตกต่างใช่ไหม อย่างน้อยก็ยังคงรวบรวมโดยไม่มีปัญหาใด ๆ

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

อย่างไรก็ตามในบรรทัดที่สองฉันพยายามกำหนดผู้รับมอบสิทธิ์รายแรกให้ผู้แทนรายอื่น แม้คิดว่าประเภทของผู้ร่วมประชุมครั้งที่ 2 CS0029: Cannot implicitly convert type 'ExceptionHandler1' to 'ExceptionHandler2'มีตรงเดียวกันพารามิเตอร์และผลตอบแทนประเภทคอมไพเลอร์ให้ข้อผิดพลาด

บางทีนี่อาจทำให้ชัดเจน:

public static bool IsNegative(int x)
{
    return x < 0;
}

static void Main(string[] args)
{
    Predicate<int> p = IsNegative;
    Func<int, bool> f = IsNegative;

    p = f; // Not allowed
}

วิธีการของฉันIsNegativeเป็นสิ่งที่ดีอย่างสมบูรณ์ในการกำหนดให้กับpและfตัวแปรตราบใดที่ฉันทำโดยตรง แต่ฉันไม่สามารถกำหนดตัวแปรตัวใดตัวหนึ่งให้กับอีกตัวได้


แต่ถึงกระนั้นการเปลี่ยนประเภทพารามิเตอร์จาก Func <T, bool> เป็น Predicate <T> และย้อนกลับดูเหมือนจะไม่สร้างความแตกต่างใช่ไหม อย่างน้อยก็ยังคงรวบรวมโดยไม่มีปัญหาใด ๆ
Svish

ดูเหมือนว่า MS กำลังพยายามกีดกันนักพัฒนาไม่ให้คิดสิ่งเหล่านี้เหมือนกัน ฉันสงสัยว่าทำไม?
Matt Kocaj

1
การสลับเปลี่ยนพารามิเตอร์ชนิดไม่สร้างความแตกต่างถ้าการแสดงออกที่คุณจะผ่านมันได้ถูกกำหนดให้แยกต่างหากเพื่อเรียกวิธีตั้งแต่นั้นก็จะมีการพิมพ์เป็นอย่างใดอย่างหนึ่งFunc<T, bool>หรือPredicate<T>มากกว่าที่มีคนประเภทที่อ้างถึงโดยคอมไพเลอร์
Adam Ralph

30

คำแนะนำ (ใน 3.5 และสูงกว่า) คือการใช้Action<...>และFunc<...>- สำหรับ "ทำไม" - ข้อดีอย่างหนึ่งคือ " Predicate<T>" มีความหมายเฉพาะถ้าคุณรู้ว่า "ภาคแสดง" หมายถึงอะไร - ไม่เช่นนั้นคุณต้องดูที่วัตถุ - เบราว์เซอร์ (ฯลฯ ) เพื่อค้นหาลายเซ็น

ตรงกันข้ามFunc<T,bool>ตามรูปแบบมาตรฐาน ฉันสามารถบอกได้ทันทีว่านี่เป็นฟังก์ชั่นที่รับTและส่งคืน a bool- ไม่จำเป็นต้องเข้าใจคำศัพท์ใด ๆ - เพียงแค่ใช้การทดสอบความจริงของฉัน

สำหรับ "เพรดิเคต" สิ่งนี้อาจใช้ได้ แต่ฉันขอขอบคุณที่พยายามทำให้เป็นมาตรฐาน นอกจากนี้ยังช่วยให้ความเท่าเทียมกันจำนวนมากด้วยวิธีการที่เกี่ยวข้องในพื้นที่นั้น

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