นิพจน์ C # แลมบ์ดา: ทำไมฉันจึงควรใช้


309

ฉันอ่านเอกสารMicrosoft Lambda Expressionอย่างรวดเร็ว

ตัวอย่างประเภทนี้ช่วยให้ฉันเข้าใจได้ดีขึ้นถึง:

delegate int del(int i);
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25

ถึงกระนั้นฉันก็ยังไม่เข้าใจว่าทำไมมันถึงเป็นนวัตกรรม มันเป็นแค่วิธีการหนึ่งที่ตายเมื่อ "ตัวแปร method" จบลงใช่ไหม เหตุใดฉันจึงควรใช้วิธีนี้แทนวิธีการจริง


3
สำหรับบรรดาของคุณที่มาที่หน้านี้และไม่ทราบว่าสิ่งที่delegateอยู่ใน C # ฉันขอแนะนำให้อ่านก่อนที่จะอ่านส่วนที่เหลือของหน้านี้: stackoverflow.com/questions/2082615/ …
Kolob Canyon

คำตอบ:


281

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

ต่อไปนี้เป็นตัวอย่างของการแสดงออกของLINQ to Objectsโดยใช้ผู้ได้รับมอบหมายนิรนามจากนั้นแลมบ์ดานิพจน์เพื่อแสดงให้เห็นว่าตาของพวกเขาง่ายขึ้นเพียงใด:

// anonymous delegate
var evens = Enumerable
                .Range(1, 100)
                .Where(delegate(int x) { return (x % 2) == 0; })
                .ToList();

// lambda expression
var evens = Enumerable
                .Range(1, 100)
                .Where(x => (x % 2) == 0)
                .ToList();

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

ต้นไม้ Expressionเป็นคุณลักษณะใหม่ที่ทรงพลังมากของ C # 3.0 ที่อนุญาตให้ API ดูโครงสร้างของนิพจน์แทนที่จะได้รับการอ้างอิงถึงวิธีการที่สามารถดำเนินการได้ API เพียงแค่สร้างพารามิเตอร์ผู้รับมอบสิทธิ์ให้เป็นExpression<T>พารามิเตอร์และคอมไพเลอร์จะสร้างทรีนิพจน์จากแลมบ์ดาแทนที่จะเป็นตัวแทนที่ไม่ระบุชื่อ:

void Example(Predicate<int> aDelegate);

เรียกว่า:

Example(x => x > 5);

กลายเป็น:

void Example(Expression<Predicate<int>> expressionTree);

หลังจะได้รับผ่านการเป็นตัวแทนของที่ต้นไม้ไวยากรณ์นามธรรมx > 5ที่อธิบายถึงการแสดงออก LINQ ไปยัง SQL อาศัยลักษณะการทำงานนี้เพื่อให้สามารถเปลี่ยนนิพจน์ C # ในนิพจน์ SQL ที่ต้องการสำหรับการกรอง / การสั่งซื้อ / ฯลฯ ในฝั่งเซิร์ฟเวอร์


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

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

138

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

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

 string person = people.Find(person => person.Contains("Joe"));

กับ

 public string FindPerson(string nameContains, List<string> persons)
 {
     foreach (string person in persons)
         if (person.Contains(nameContains))
             return person;
     return null;
 }

สิ่งเหล่านี้เทียบเท่ากับหน้าที่


8
จะมีการกำหนดวิธีการค้นหา () เพื่อจัดการกับการแสดงออกแลมบ์ดานี้อย่างไร
Patrick Desjardins

3
เพรดิเคต <T> คือสิ่งที่เมธอด Find คาดหวัง
Darren Kopp

1
เนื่องจากการแสดงออกแลมบ์ดาของฉันตรงกับสัญญาของเพรดิเคต <T> วิธีการค้นหา () จึงยอมรับ
โจเซฟไดเกิล

คุณหมายถึง "string person = people.Find (persons => persons.Contains (" Joe "));"
Gern Blanston

5
@FKCoder ไม่เขาไม่แม้ว่ามันอาจจะชัดเจนกว่าถ้าเขาพูดว่า "string person = people.Find (p => p.Contains (" Joe "))";
Benjol

84

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

private ComboBox combo;
private Label label;

public CreateControls()
{
    combo = new ComboBox();
    label = new Label();
    //some initializing code
    combo.SelectedIndexChanged += new EventHandler(combo_SelectedIndexChanged);
}

void combo_SelectedIndexChanged(object sender, EventArgs e)
{
    label.Text = combo.SelectedValue;
}

ด้วยการแสดงออกแลมบ์ดาคุณสามารถใช้มันได้เช่นนี้:

public CreateControls()
{
    ComboBox combo = new ComboBox();
    Label label = new Label();
    //some initializing code
    combo.SelectedIndexChanged += (s, e) => {label.Text = combo.SelectedValue;};
}

ง่ายกว่ามาก.


ในตัวอย่างแรกทำไมไม่ลองส่งผู้ส่งและรับค่า
แอนดรู

@Andrew: ในตัวอย่างง่าย ๆ นี้มันไม่จำเป็นต้องใช้ผู้ส่งเพราะมีเพียงหนึ่งองค์ประกอบที่เป็นปัญหาและการใช้เขตข้อมูลโดยตรงจะบันทึกการส่งซึ่งจะช่วยเพิ่มความชัดเจน ในสถานการณ์จริงฉันเองก็ชอบที่จะใช้ผู้ส่งแทนเช่นกัน โดยปกติฉันจะใช้ตัวจัดการเหตุการณ์หนึ่งเหตุการณ์สำหรับหลาย ๆ เหตุการณ์ถ้าเป็นไปได้ดังนั้นฉันต้องระบุผู้ส่งจริง
Chris Tophski

35

แลมบ์ดาล้างไวยากรณ์ตัวแทนที่ไม่ระบุชื่อของ C # 2.0 ... ตัวอย่างเช่น

Strings.Find(s => s == "hello");

เสร็จใน C # 2.0 ดังนี้:

Strings.Find(delegate(String s) { return s == "hello"; });

หน้าที่พวกเขาทำสิ่งเดียวกันแน่นอนมันเป็นเพียงไวยากรณ์ที่กระชับมากขึ้น


3
พวกเขาไม่ได้ค่อนข้างสิ่งเดียวกัน - เป็น @Neil วิลเลียมส์ชี้ให้คุณสามารถแยก AST lambdas' ใช้ไม้แสดงออกในขณะที่วิธีการที่ไม่ระบุชื่อไม่สามารถใช้วิธีการเดียวกัน
ljs

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

29

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

List<string> strings = new List<string>();
strings.Add("Good");
strings.Add("Morning")
strings.Add("Starshine");
strings.Add("The");
strings.Add("Earth");
strings.Add("says");
strings.Add("hello");

strings.Find(s => s == "hello");

รหัสนี้จะค้นหารายการสำหรับรายการที่ตรงกับคำว่า "hello" วิธีอื่นในการทำเช่นนี้คือการส่งผู้รับมอบสิทธิ์ไปยังวิธีการค้นหาเช่นนี้:

List<string> strings = new List<string>();
strings.Add("Good");
strings.Add("Morning")
strings.Add("Starshine");
strings.Add("The");
strings.Add("Earth");
strings.Add("says");
strings.Add("hello");

private static bool FindHello(String s)
{
    return s == "hello";
}

strings.Find(FindHello);

แก้ไข :

ใน C # 2.0 สามารถทำได้โดยใช้ไวยากรณ์ตัวแทนที่ไม่ระบุชื่อ:

  strings.Find(delegate(String s) { return s == "hello"; });

แลมบ์ดาล้างไวยากรณ์นั้นอย่างมีนัยสำคัญ


2
@Jonathan Holland: ขอบคุณสำหรับการแก้ไขและเพิ่มไวยากรณ์ผู้ร่วมประชุมโดยไม่ระบุชื่อ มันเป็นตัวอย่างที่สมบูรณ์
Scott Dorman

ผู้แทนนิรนามคืออะไร // ขออภัยฉันยังใหม่กับ c #
HackerMan

1
@ แฮ็คเกอร์แมนคิดว่าเป็นตัวแทนที่ไม่ระบุชื่อเป็นฟังก์ชันที่ไม่มี "ชื่อ" คุณยังกำหนดฟังก์ชันซึ่งสามารถมีอินพุตและเอาต์พุต แต่เนื่องจากเป็นชื่อคุณจึงไม่สามารถอ้างถึงได้โดยตรง ในรหัสที่แสดงด้านบนคุณจะกำหนดวิธีการ (ซึ่งใช้stringและส่งกลับ a bool) เป็นพารามิเตอร์ของFindวิธีการนั้น
Scott Dorman

22

Microsoft ให้วิธีที่สะอาดและสะดวกกว่าแก่คุณในการสร้างผู้แทนที่ไม่ระบุชื่อที่เรียกว่านิพจน์แลมบ์ดา อย่างไรก็ตามมีความสนใจไม่มากที่ถูกจ่ายให้กับส่วนของการแสดงออกของคำสั่งนี้ Microsoft เปิดตัว namespace ทั้งหมดSystem.Linq.Expressionsซึ่งมีคลาสเพื่อสร้างแผนภูมินิพจน์ตามแลมบ์ดานิพจน์ ต้นไม้นิพจน์ประกอบด้วยวัตถุที่ใช้แทนตรรกะ ตัวอย่างเช่น x = y + z เป็นนิพจน์ที่อาจเป็นส่วนหนึ่งของแผนภูมินิพจน์ใน. Net ลองพิจารณาตัวอย่าง (แบบง่าย) ต่อไปนี้:

using System;
using System.Linq;
using System.Linq.Expressions;


namespace ExpressionTreeThingy
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<int, int>> expr = (x) => x + 1; //this is not a delegate, but an object
            var del = expr.Compile(); //compiles the object to a CLR delegate, at runtime
            Console.WriteLine(del(5)); //we are just invoking a delegate at this point
            Console.ReadKey();
        }
    }
}

ตัวอย่างนี้เล็กน้อย และฉันแน่ใจว่าคุณกำลังคิดว่า "นี่ไร้ประโยชน์เพราะฉันสามารถสร้างผู้แทนโดยตรงแทนที่จะสร้างการแสดงออกและรวบรวมมันในขณะทำงาน" และคุณจะถูกต้อง แต่นี่เป็นพื้นฐานสำหรับการแสดงออกของต้นไม้ มีจำนวนนิพจน์ที่มีอยู่ในเนมสเปซนิพจน์และคุณสามารถสร้างของคุณเอง ฉันคิดว่าคุณสามารถเห็นว่าสิ่งนี้อาจมีประโยชน์เมื่อคุณไม่ทราบว่าอัลกอริทึมควรออกแบบหรือรวบรวมเวลาอย่างไร ฉันเห็นตัวอย่างจากการใช้สิ่งนี้เพื่อเขียนเครื่องคำนวณทางวิทยาศาสตร์ คุณสามารถใช้กับระบบBayesianหรือการโปรแกรมเชิงพันธุกรรม(AI). สองสามครั้งในอาชีพการงานของฉันฉันต้องเขียนฟังก์ชั่นที่คล้ายกับ Excel ที่อนุญาตให้ผู้ใช้ป้อนนิพจน์อย่างง่าย (เพิ่มเติม, การทำงานย่อย ฯลฯ ) เพื่อทำงานกับข้อมูลที่มีอยู่ ใน pre-.Net 3.5 ฉันต้องหันไปใช้ภาษาสคริปต์บางภาษาภายนอก C # หรือต้องใช้ฟังก์ชั่นการเปล่งรหัสเพื่อสะท้อนเพื่อสร้างรหัส. Net ในทันที ตอนนี้ฉันจะใช้ต้นไม้แสดงออก


12

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

และไม่ใช่นวัตกรรมจริงๆ LISP มีฟังก์ชั่นแลมบ์ดาประมาณ 30 ปีขึ้นไป


6

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

ตัวอย่างเช่น: ฟังก์ชันทั่วไปเพื่อคำนวณเวลาที่ใช้โดยการเรียกเมธอด (เช่นActionในที่นี่)

public static long Measure(Action action)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    action();
    sw.Stop();
    return sw.ElapsedMilliseconds;
}

และคุณสามารถเรียกใช้วิธีการด้านบนโดยใช้การแสดงออกแลมบ์ดาดังนี้

var timeTaken = Measure(() => yourMethod(param));

Expression ช่วยให้คุณได้รับค่าตอบแทนจากวิธีการของคุณและค่าพารามิเตอร์เช่นกัน

var timeTaken = Measure(() => returnValue = yourMethod(param, out outParam));

5

การแสดงออกของแลมบ์ดาเป็นวิธีรัดกุมเพื่อแสดงวิธีการที่ไม่ระบุชื่อ ทั้งวิธีนิรนามและนิพจน์แลมบ์ดาอนุญาตให้คุณกำหนดวิธีการนำอินไลน์ไปใช้อย่างไรก็ตามวิธีนิรนามระบุให้คุณต้องกำหนดประเภทพารามิเตอร์และชนิดส่งคืนสำหรับวิธีการอย่างชัดเจน การแสดงออกของแลมบ์ดาใช้คุณสมบัติการอนุมานประเภทของ C # 3.0 ซึ่งทำให้คอมไพเลอร์สรุปประเภทของตัวแปรตามบริบท มันสะดวกมากเพราะมันช่วยให้เราพิมพ์เยอะมาก!


5

การแสดงออกแลมบ์ดาเป็นเหมือนวิธีที่ไม่ระบุชื่อซึ่งเขียนแทนอินสแตนซ์ของผู้ร่วมประชุม

delegate int MyDelagate (int i);
MyDelagate delSquareFunction = x => x * x;

พิจารณาการแสดงออกแลมบ์ดา x => x * x;

ค่าพารามิเตอร์อินพุตคือ x (ด้านซ้ายของ =>)

ตรรกะของฟังก์ชันคือ x * x (ทางด้านขวาของ =>)

โค้ดของแลมบ์ดาแลมบ์ดาสามารถเป็นบล็อกคำสั่งแทนที่จะเป็นนิพจน์

x => {return x * x;};

ตัวอย่าง

หมายเหตุ: Funcเป็นตัวแทนทั่วไปที่กำหนดไว้ล่วงหน้า

    Console.WriteLine(MyMethod(x => "Hi " + x));

    public static string MyMethod(Func<string, string> strategy)
    {
        return strategy("Lijo").ToString();
    }

อ้างอิง

  1. ผู้รับมอบสิทธิ์และอินเตอร์เฟสสามารถใช้แทนกันได้อย่างไร?

4

บ่อยครั้งที่คุณใช้ฟังก์ชั่นการทำงานในที่เดียวดังนั้นการสร้างวิธีการที่จะทำให้ชั้นเรียนง่ายขึ้น


3

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

ในทำนองเดียวกันทำไมคุณต้องใช้ foreach คุณสามารถทำทุกอย่างใน foreach ด้วยล้วนสำหรับ loop หรือเพียงใช้ IEnumerable โดยตรง คำตอบ: คุณไม่ต้องการแต่มันทำให้โค้ดของคุณอ่านง่ายขึ้น


0

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

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

การสร้าง LINQ API บนผู้รับมอบสิทธิ์ล้วนเป็นไปไม่ได้เพราะมันต้องมีการรวมแผนภูมิการแสดงออกด้วยกันก่อนที่จะประเมินพวกเขา

ในปี 2559 ภาษายอดนิยมส่วนใหญ่ได้รับการสนับสนุนการแสดงออกแลมบ์ดาและ C # เป็นหนึ่งในผู้บุกเบิกในวิวัฒนาการนี้ท่ามกลางภาษาที่จำเป็นหลัก ๆ


0

นี่อาจเป็นคำอธิบายที่ดีที่สุดเกี่ยวกับสาเหตุที่ต้องใช้แลมบ์ดานิพจน์ -> https://youtu.be/j9nj5dTo54Q

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


0

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

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

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