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 Webサービスの参照を生成しまし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; });してください。
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#の他の場所には存在しないものです。(数値リテラルは多少似ています。)

まったく同じラムダリテラルができるいずれかのあなたが実行できることを無名関数(すなわち、Aに評価Func/ Actionまたは種類(すなわちA LINQの式ツリー)抽象構文木などで、身体の内部操作の抽象表現。

後者は、たとえば、LINQ to SQL、LINQ to XMLなどの機能です。ラムダ実行可能コードに評価されず、LINQ式ツリーに評価され、LINQプロバイダーはそれらの式ツリーを使用して、ラムダの本体が実行していて、SQLクエリなどを生成します。

あなたの場合、ラムダリテラルがFuncLINQ式に評価されるかどうかをコンパイラが知る方法はありません。それが、ジョナサンバークレイの答えが機能する理由です。ラムダ式に型を与えるため、コンパイラー、内部のコードを表す評価されていないLINQ式ツリーでなく、ラムダの本体Func実行するコンパイル済みコードが必要であることを認識しています。ラムダの体。


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

拡張メソッドの設計ガイドラインのいくつかに違反してもかまわない場合は、null条件演算子と組み合わせた拡張メソッドを使用すると、?.かなり遠くまで移動できます。

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.