มีวิธีในการประกาศแลมบ์ดา C # และเรียกมันทันทีหรือไม่?


29

เป็นไปได้ที่จะประกาศฟังก์ชั่นแลมบ์ดาและเรียกมันว่า:

Func<int, int> lambda = (input) => { return 1; };
int output = lambda(0);

ฉันสงสัยว่ามันเป็นไปได้ที่จะทำเช่นนั้นในหนึ่งบรรทัดเช่นบางสิ่งบางอย่าง

int output = (input) => { return 1; }(0);

ซึ่งให้ข้อผิดพลาดคอมไพเลอร์ "คาดว่าชื่อวิธี" กำลังส่งไปที่Func<int, int>ไม่ทำงาน:

int output = (Func<int, int>)((input) => { return 1; })(0);

ให้ข้อผิดพลาดเดียวกันและด้วยเหตุผลที่กล่าวถึงด้านล่างฉันต้องการหลีกเลี่ยงการระบุประเภทอาร์กิวเมนต์อินพุต (อย่างแรกint) อย่างชัดเจน


คุณอาจสงสัยว่าทำไมint output = 1;ฉันต้องการที่จะทำเช่นนี้แทนเพียงฝังโค้ดโดยตรงตัวอย่างเช่น เหตุผลมีดังต่อไปนี้: ฉันได้สร้างการอ้างอิงสำหรับเว็บเซอร์วิส SOAP ด้วยsvcutilเนื่องจากองค์ประกอบซ้อนกันสร้างชื่อคลาสที่ยาวมากซึ่งฉันต้องการหลีกเลี่ยงการพิมพ์ออกมา ดังนั้นแทนที่จะ

var o = await client.GetOrderAsync(request);
return new Order {
    OrderDate = o.OrderDate,
    ...
    Shipments = o.Shipment_Order == null ? new Shipment[0]
        o.Shipment_Order.Select(sh => new Shipment {
            ShipmentID = sh.ShipmentID,
            ...
            Address = CreateAddress(sh.ReceiverAddress_Shipment);
        }).ToArray()
};

และCreateAddress(GetOrderResultOrderShipment_OrderShipmentShipment_Address address)วิธีการแยกต่างหาก(ชื่อจริงนั้นยาวกว่าและฉันมีการควบคุมที่ จำกัด มากเกี่ยวกับแบบฟอร์ม) ฉันต้องการเขียน

var o = await client.GetOrderAsync(request);
return new Order {
    OrderDate = o.OrderDate,
    ...
    Shipments = o.Shipment_Order == null ? new Shipment[0]
        o.Shipment_Order.Select(sh => new Shipment {
            ShipmentID = sh.ShipmentID,
            ...
            Address = sh.ReceiverAddress_Shipment == null ? null : () => {
                var a = sh.ReceiverAddress_Shipment.Address;
                return new Address {
                    Street = a.Street
                    ...
                };
            }()
        }).ToArray()
};

ฉันรู้ว่าฉันสามารถเขียน

Address = sh.ReceiverAddress_Shipment == null ? null : new Address {
    Street = sh.ReceiverAddress_Shipment.Address.Street,
    ...
}

แต่ถึงกระนั้นก็ตาม ( sh.ReceiverAddress_Shipment.Addressส่วน) ก็จะเกิดขึ้นซ้ำ ๆ กันถ้ามีหลายสาขา ประกาศแลมบ์ดาและเรียกมันทันทีว่ามันจะมีตัวอักษรน้อยลงที่สง่างามกว่าที่จะเขียน


int output = ((Func<int>) (() => { return 1; }))();
Dmitry Bychenko

ทำไมไม่เพียงแค่เขียนเสื้อคลุมตัวเล็ก ๆpublic T Exec<T>(Func<T> func) => return func();แล้วใช้มันแบบนี้: int x = Exec(() => { return 1; });สำหรับฉันแล้วฉันอ่านได้ดีกว่าการหล่อด้วย parens ทั้งหมด
germi

@germi เป็นความคิดที่ดี แต่ให้ฉัน "อาร์กิวเมนต์ประเภทสำหรับวิธี Exec ไม่สามารถอนุมานจากการใช้งานได้"
Glorfindel

@Glorfindel คุณทำอะไรผิดไปแล้ว: dotnetfiddle.net/oku7eX
canton7

@ canton7 เพราะจริง ๆ แล้วฉันใช้แลมบ์ดากับพารามิเตอร์อินพุต ... ขอบคุณมันใช้งานได้แล้ว
Glorfindel

คำตอบ:


29

แทนที่จะพยายามโยนแลมด้าฉันเสนอให้คุณใช้ฟังก์ชั่นตัวช่วยเล็ก ๆ :

public static TOut Exec<TIn, TOut>(Func<TIn, TOut> func, TIn input) => func(input);

int x = Exec(myVar => myVar + 2, 0);ซึ่งคุณสามารถใช้เช่นนี้ นี่เป็นการอ่านที่ดีกว่าสำหรับฉันมากกว่าทางเลือกอื่น ๆ ที่แนะนำที่นี่


25

มันน่าเกลียด แต่ก็เป็นไปได้:

int output = ((Func<int, int>)(input => { return 1; }))(0);

คุณสามารถส่งได้ แต่แลมบ์ดาจะต้องอยู่ในวงเล็บ

ข้างต้นสามารถทำให้เข้าใจง่ายเช่นกัน:

int output = ((Func<int, int>)(input => 1))(0);

2
อ่าแน่นอน ฉันลองint output = (Func<int>)(() => { return 1; })();แต่นักแสดงมีลำดับความสำคัญต่ำกว่าการบังคับแลมบ์ดา
Glorfindel

มันยังคงไม่สามารถแก้ปัญหาที่ไม่ต้องการเขียนชื่อชั้นยาวมาก ๆ
Glorfindel

4

ตัวอักษรแลมบ์ดาใน C # มีความแตกต่างที่น่าสงสัยในความหมายของพวกเขาขึ้นอยู่กับประเภทของพวกเขา พวกเขามีการใช้งานมากเกินไปในประเภทการคืนสินค้าซึ่งเป็นสิ่งที่ไม่มีอยู่ในที่อื่นใน C # (ตัวอักษรตัวเลขค่อนข้างคล้ายกัน)

เดียวกันแน่นอนอักษรแลมบ์ดาสามารถทั้งประเมินผลการศึกษาไปยังฟังก์ชั่นที่ไม่ระบุชื่อที่คุณสามารถดำเนินการ (เช่นFunc/ Action) หรือเป็นตัวแทนนามธรรมของการดำเนินงานภายในของร่างกายชนิดเช่นบทคัดย่อไวยากรณ์ต้นไม้ (เช่น LINQ Expression ต้นไม้)

ตัวอย่างหลังคือวิธีที่ LINQ กับ SQL, LINQ ถึง XML และอื่น ๆ ทำงานอย่างไร: lambdas ไม่ได้ประเมินรหัสที่สามารถใช้งานได้พวกมันจะประเมินต้นไม้ LINQ Expression และผู้ให้บริการ LINQ สามารถใช้ต้นไม้ Expression เหล่านั้นเพื่อทำความเข้าใจว่า เนื้อความของแลมบ์ดากำลังทำและสร้างเช่น SQL Query จากนั้น

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


3

คุณสามารถอินไลน์ประกาศของFuncโดยการทำ

int output = (new Func<int, int>(() => { return 1; }))(0);

และเรียกมันทันที


2

คุณยังสามารถสร้างนามแฝงในSelectวิธีการ

var o = await client.GetOrderAsync(request);
return new Order {
    OrderDate = o.OrderDate,
    ...
    Shipments = o.Shipment_Order == null ? new Shipment[0]
        o.Shipment_Order.Select(sh => {
          var s = sh.ReceiverAddress_Shipment;
          var a = s.Address;
          return new Shipment {
            ShipmentID = sh.ShipmentID,
            ...
            Address = s == null ? 
                      null : 
                      new Address {
                        Street = a.Street
                        ...
                      }
          };
        }).ToArray()
};

หรือกับ??ผู้ประกอบการ

var o = await client.GetOrderAsync(request);
return new Order {
    OrderDate = o.OrderDate,
    ...
    Shipments = o.Shipment_Order?.Select(sh => {
        var s = sh.ReceiverAddress_Shipment;
        var a = s.Address;
        return new Shipment {
            ShipmentID = sh.ShipmentID,
            ...
            Address = s == null ? 
                      null : 
                      new Address {
                          Street = a.Street
                          ...
                      }
        };
    }).ToArray() ?? new Shipment[0]
};

1

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

public static class Extensions
{
    public static TOut Map<TIn, TOut>(this TIn value, Func<TIn, TOut> map)
        where TIn : class
        => value == null ? default(TOut) : map(value);

    public static IEnumerable<T> OrEmpty<T>(this IEnumerable<T> items)
        => items ?? Enumerable.Empty<T>();
}

จะให้สิ่งนี้กับคุณ:

return new Order
{
    OrderDate = o.OrderDate,
    Shipments = o.Shipment_Order.OrEmpty().Select(sh => new Shipment
    {
        ShipmentID = sh.ShipmentID,
        Address = sh.ReceiverAddress_Shipment?.Address.Map(a => new Address
        {
            Street = a.Street
        })
    }).ToArray()
};

และถ้าคุณต้องการอาร์เรย์ส่วนใหญ่แล้วให้แทนที่ToArrayวิธีส่วนขยายเพื่อแค็ปซูลการเรียกใช้เมธอดอีกสองสามรายการ:

public static TOut[] ToArray<TIn, TOut>(this IEnumerable<TIn> items, Func<TIn, TOut> map)
    => items == null ? new TOut[0] : items.Select(map).ToArray();

ที่เกิดขึ้นใน:

return new Order
{
    OrderDate = o.OrderDate,
    Shipments = o.Shipment_Order.ToArray(sh => new Shipment
    {
        ShipmentID = sh.ShipmentID,
        Address = sh.ReceiverAddress_Shipment?.Address.Map(a => new Address
        {
            Street = a.Street
        })
    })
};
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.