Apakah ada cara untuk mendeklarasikan C # lambda dan segera menyebutnya?

29

Mungkin untuk mendeklarasikan fungsi lambda dan segera menyebutnya:

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

Saya bertanya-tanya apakah mungkin untuk melakukannya dalam satu baris, misalnya sesuatu seperti

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

yang memberikan kesalahan kompilator "Nama metode yang diharapkan". Casting untuk Func<int, int>tidak bekerja:

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

memberikan kesalahan yang sama, dan untuk alasan yang disebutkan di bawah ini saya ingin menghindari keharusan untuk secara eksplisit menentukan jenis argumen input (yang pertama int).


Anda mungkin bertanya - tanya mengapa saya ingin melakukan ini, daripada hanya menyematkan kode secara langsung, misalnya int output = 1;. Alasannya adalah sebagai berikut: Saya telah membuat referensi untuk layanan web dengan SOAP svcutil, yang karena elemen bersarang menghasilkan nama kelas yang sangat panjang, yang saya ingin hindari mengetik. Jadi, bukannya

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()
};

dan CreateAddress(GetOrderResultOrderShipment_OrderShipmentShipment_Address address)metode yang terpisah (nama asli bahkan lebih lama, dan saya memiliki kontrol yang sangat terbatas tentang formulir), saya ingin menulis

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()
};

Saya tahu saya bisa menulis

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

tetapi bahkan itu ( sh.ReceiverAddress_Shipment.Addressbagian) menjadi sangat berulang jika ada banyak bidang. Mendeklarasikan lambda dan segera menyebutnya akan lebih elegan untuk menulis karakter.

Glorfindel
sumber
int output = ((Func<int>) (() => { return 1; }))();
Dmitry Bychenko
Mengapa tidak hanya menulis bungkus kecil: public T Exec<T>(Func<T> func) => return func();dan gunakan seperti ini: int x = Exec(() => { return 1; });Bagi saya itu jauh lebih bagus daripada casting dengan semua parensnya.
germi
@ germi ide bagus, tapi itu memberi saya "Argumen tipe untuk metode Exec tidak dapat disimpulkan dari penggunaan."
Glorfindel
@Glorfindel Anda melakukan kesalahan, lalu: dotnetfiddle.net/oku7eX
canton7
@ canton7 karena saya sebenarnya menggunakan lambda dengan parameter input ... Terima kasih, ini berfungsi sekarang.
Glorfindel

Jawaban:

29

Alih-alih mencoba melemparkan lambda, saya sarankan Anda menggunakan fungsi pembantu kecil:

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

yang kemudian bisa menggunakan seperti ini: int x = Exec(myVar => myVar + 2, 0);. Bagi saya ini jauh lebih baik daripada alternatif yang disarankan di sini.

germi
sumber
25

Itu jelek, tapi itu mungkin:

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

Anda dapat memilih, tetapi lambda harus dimasukkan dalam tanda kurung.

Di atas dapat disederhanakan juga:

int output = ((Func<int, int>)(input => 1))(0);
Johnathan Barclay
sumber
2
Ah, tentu saja. Saya hanya mencoba int output = (Func<int>)(() => { return 1; })();tetapi para pemain memiliki prioritas lebih rendah daripada eksekusi lambda.
Glorfindel
Itu masih tidak menyelesaikan masalah tidak ingin menulis nama kelas yang sangat panjang.
Glorfindel
4

Literatur Lambda dalam bahasa C # memiliki perbedaan yang aneh karena artinya tergantung pada jenisnya. Mereka pada dasarnya kelebihan beban pada tipe pengembalian mereka yang merupakan sesuatu yang tidak ada di tempat lain di C #. (Numerik literal agak mirip.)

The persis sama lambda literal dapat baik mengevaluasi fungsi anonim yang Anda dapat mengeksekusi (yaitu Func/ Action) atau representasi abstrak dari operasi dalam Tubuh, jenis seperti Abstrak Sintaks Pohon (yaitu LINQ Ekspresi Pohon).

Yang terakhir adalah, misalnya, bagaimana LINQ to SQL, LINQ to XML, dll bekerja: lambda tidak mengevaluasi ke kode yang dapat dieksekusi, mereka mengevaluasi ke Pohon Ekspresi LINQ, dan penyedia LINQ kemudian dapat menggunakan Pohon Ekspresi untuk memahami apa yang tubuh lambda sedang melakukan dan menghasilkan misalnya SQL Query dari itu.

Dalam kasus Anda, tidak ada cara bagi kompiler untuk mengetahui apakah literal lambda seharusnya dievaluasi ke Funcatau Ekspresi LINQ. Itulah sebabnya jawaban Johnathan Barclay bekerja: ia memberikan tipe pada ekspresi lambda dan oleh karena itu, kompiler tahu bahwa Anda menginginkan Funckode yang dikompilasi yang mengeksekusi tubuh lambda Anda alih-alih Pohon Ekspresi LINQ yang tidak dievaluasi yang mewakili kode di dalamnya tubuh lambda.

Jörg W Mittag
sumber
3

Anda dapat menguraikan deklarasi Funcdengan melakukan

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

dan segera memintanya.

phuzi
sumber
2

Anda juga dapat membuat alias dalam Selectmetode ini

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()
};

atau dengan ??operator

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]
};
Cyril Durand
sumber
1

Jika Anda tidak keberatan melanggar beberapa pedoman desain metode ekstensi, metode ekstensi yang dikombinasikan dengan operator kondisi-nol ?.dapat membawa Anda cukup jauh:

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>();
}

akan memberi Anda ini:

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()
};

dan jika Anda sebagian besar membutuhkan array, maka ganti ToArraymetode ekstensi untuk merangkum beberapa metode panggilan lagi:

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

yang menghasilkan:

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
        })
    })
};
Konstantin Spirin
sumber