Existe-t-il un moyen de déclarer un lambda C # et de l'appeler immédiatement?

29

Il est possible de déclarer une fonction lambda et de l'appeler immédiatement:

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

Je me demande s'il est possible de le faire sur une seule ligne, par exemple quelque chose comme

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

ce qui donne une erreur de compilation "Nom de méthode attendu". La diffusion vers Func<int, int>ne fonctionne pas non plus:

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

donne la même erreur, et pour les raisons mentionnées ci-dessous, je voudrais éviter d'avoir à spécifier explicitement le type d'argument d'entrée (le premier int).


Vous vous demandez probablement pourquoi je veux faire cela, au lieu de simplement intégrer le code directement, par exemple int output = 1;. La raison est la suivante: j'ai généré une référence pour un service Web SOAP avec svcutillequel, en raison des éléments imbriqués, génère des noms de classe extrêmement longs, que j'aimerais éviter d'avoir à taper. Donc au lieu de

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

et une CreateAddress(GetOrderResultOrderShipment_OrderShipmentShipment_Address address)méthode distincte (les vrais noms sont encore plus longs, et j'ai un contrôle très limité sur le formulaire), je voudrais écrire

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

Je sais que je pourrais écrire

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

mais même cela (la sh.ReceiverAddress_Shipment.Addresspartie) devient très répétitif s'il y a beaucoup de champs. Déclarer un lambda et l'appeler immédiatement serait plus élégant, moins de caractères à écrire.

Glorfindel
la source
int output = ((Func<int>) (() => { return 1; }))();
Dmitry Bychenko
Pourquoi ne pas simplement écrire un petit wrapper: public T Exec<T>(Func<T> func) => return func();et l'utiliser comme ceci: int x = Exec(() => { return 1; });Cela me semble beaucoup plus agréable que le casting avec toutes ses parens.
germi
@germi bonne idée, mais cela me donne "Les arguments de type pour la méthode Exec ne peuvent pas être déduits de l'utilisation."
Glorfindel
@Glorfindel Vous avez fait quelque chose de mal, alors: dotnetfiddle.net/oku7eX
canton7
@ canton7 parce que j'utilise un lambda avec un paramètre d'entrée ... Merci, ça marche maintenant.
Glorfindel

Réponses:

29

Au lieu d'essayer de lancer le lambda, je vous propose d'utiliser une petite fonction d'aide:

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

que vous pouvez ensuite utiliser comme ceci: int x = Exec(myVar => myVar + 2, 0);. Cela me semble beaucoup plus agréable que les alternatives suggérées ici.

germi
la source
25

C'est moche, mais c'est possible:

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

Vous pouvez transtyper, mais le lambda doit être placé entre parenthèses.

Ce qui précède peut également être simplifié:

int output = ((Func<int, int>)(input => 1))(0);
Johnathan Barclay
la source
2
Ah, bien sûr. J'ai seulement essayé int output = (Func<int>)(() => { return 1; })();mais la distribution a une priorité inférieure à l'exécution lambda.
Glorfindel
Cela ne résout toujours pas le problème de ne pas vouloir écrire les noms de classe extrêmement longs.
Glorfindel
4

Les littéraux lambda en C # ont une curieuse distinction en ce que leur signification dépend de leur type. Ils sont essentiellement surchargés sur leur type de retour qui est quelque chose qui n'existe nulle part ailleurs en C #. (Les littéraux numériques sont quelque peu similaires.)

Le même littéral lambda peut être évalué comme une fonction anonyme que vous pouvez exécuter (c'est-à-dire a Func/ Action) ou une représentation abstraite des opérations à l'intérieur du corps, un peu comme un arbre de syntaxe abstraite (c'est-à-dire un arbre d'expression LINQ).

Ce dernier est, par exemple, comment LINQ to SQL, LINQ to XML, etc. fonctionnent: les lambdas n'évaluent pas en code exécutable, ils évaluent en LINQ Expression Trees, et le fournisseur LINQ peut ensuite utiliser ces Expression Trees pour comprendre ce que le le corps du lambda fait et génère par exemple une requête SQL à partir de cela.

Dans votre cas, il n'y a aucun moyen pour le compilateur de savoir si le littéral lambda est censé être évalué en une Funcou une expression LINQ. C'est pourquoi la réponse de Johnathan Barclay fonctionne: elle donne un type à l'expression lambda et par conséquent, le compilateur sait que vous voulez un Funccode compilé qui exécute le corps de votre lambda au lieu d'un arbre d'expression LINQ non évalué qui représente le code à l'intérieur le corps de la lambda.

Jörg W Mittag
la source
3

Vous pouvez aligner la déclaration du Funcen faisant

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

et l'invoquer immédiatement.

phuzi
la source
2

Vous pouvez également créer l'alias dans la Selectméthode

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

ou avec l' ??opérateur

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
la source
1

Si cela ne vous dérange pas de violer quelques-unes des directives de conception des méthodes d'extension, les méthodes d'extension combinées à un opérateur conditionnel nul ?.peuvent vous mener raisonnablement loin:

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

vous donnera ceci:

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

et si vous avez principalement besoin de tableaux, remplacez la ToArrayméthode d'extension pour encapsuler quelques appels de méthode supplémentaires:

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

résultant en:

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
la source