web-dev-qa-db-fra.com

Comment utilisez-vous Func <> et Action <> lors de la conception d'applications?

Tous les exemples que je peux trouver sur Func <> et Action <> sont simple comme dans celui ci-dessous où vous voyez comment ils fonctionnent techniquement mais j'aimerais les voir utilisés dans des exemples où ils résolvent des problèmes qui auparavant ne pouvaient pas être résolus ou ne pouvaient être résolus que de manière plus complexe, c'est-à-dire que je sais comment ils fonctionnent et je peux voir qu'ils sont laconique et puissant, donc je veux les comprendre dans un au sens large de quels types de problèmes ils résolvent et comment je pourrais les utiliser dans la conception d'applications.

De quelles manières (modèles) utilisez-vous Func <> et Action <> pour résoudre de vrais problèmes?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestFunc8282
{
    class Program
    {
        static void Main(string[] args)
        {
            //func with delegate
            Func<string, string> convert = delegate(string s)
            {
                return s.ToUpper();
            };

            //func with lambda
            Func<string, string> convert2 = s => s.Substring(3, 10);

            //action
            Action<int,string> recordIt = (i,title) =>
                {
                    Console.WriteLine("--- {0}:",title);
                    Console.WriteLine("Adding five to {0}:", i);
                    Console.WriteLine(i + 5);
                };

            Console.WriteLine(convert("This is the first test."));
            Console.WriteLine(convert2("This is the second test."));
            recordIt(5, "First one");
            recordIt(3, "Second one");

            Console.ReadLine();

        }
    }
}
69
Edward Tanguay

Ils sont également pratiques pour refactoriser les instructions de commutateur.

Prenons l'exemple (quoique simple) suivant:

public void Move(int distance, Direction direction)
{
    switch (direction)
    {
        case Direction.Up :
            Position.Y += distance;
            break;
        case Direction.Down:
            Position.Y -= distance;
            break;
        case Direction.Left:
            Position.X -= distance;
            break;
        case Direction.Right:
            Position.X += distance;
            break;
    }
}

Avec un délégué Action, vous pouvez le refactoriser comme suit:

static Something()
{
    _directionMap = new Dictionary<Direction, Action<Position, int>>
    {
        { Direction.Up,    (position, distance) => position.Y +=  distance },
        { Direction.Down,  (position, distance) => position.Y -=  distance },
        { Direction.Left,  (position, distance) => position.X -=  distance },
        { Direction.Right, (position, distance) => position.X +=  distance },
    };
}

public void Move(int distance, Direction direction)
{
    _directionMap[direction](this.Position, distance);
}
56
Craig Vermeer

J'utilise tout le temps les délégués Action et Func. Je les déclare généralement avec la syntaxe lambda pour économiser de l'espace et les utilise principalement pour réduire la taille des grandes méthodes. Lorsque je passe en revue ma méthode, des segments de code similaires se détachent parfois. Dans ces cas, j'encapsule les segments de code similaires dans Action ou Func. L'utilisation du délégué réduit le code redondant, donne une belle signature au segment de code et peut facilement être promue en méthode si nécessaire.

J'écrivais du code Delphi et vous pouviez déclarer une fonction dans une fonction. Action et Func accomplissent ce même comportement pour moi en c #.

Voici un exemple de contrôles de repositionnement avec un délégué:

private void Form1_Load(object sender, EventArgs e)
{
    //adjust control positions without delegate
    int left = 24;

    label1.Left = left;
    left += label1.Width + 24;

    button1.Left = left;
    left += button1.Width + 24;

    checkBox1.Left = left;
    left += checkBox1.Width + 24;

    //adjust control positions with delegate. better
    left = 24;
    Action<Control> moveLeft = c => 
    {
        c.Left = left;
        left += c.Width + 24; 
    };
    moveLeft(label1);
    moveLeft(button1);
    moveLeft(checkBox1);
}
14
Steve

Une chose pour laquelle je l'utilise est la mise en cache d'appels de méthode coûteux qui ne changent jamais avec la même entrée:

public static Func<TArgument, TResult> Memoize<TArgument, TResult>(this Func<TArgument, TResult> f)
{
    Dictionary<TArgument, TResult> values;

    var methodDictionaries = new Dictionary<string, Dictionary<TArgument, TResult>>();

    var name = f.Method.Name;
    if (!methodDictionaries.TryGetValue(name, out values))
    {
        values = new Dictionary<TArgument, TResult>();

        methodDictionaries.Add(name, values);
    }

    return a =>
    {
        TResult value;

        if (!values.TryGetValue(a, out value))
        {
            value = f(a);
            values.Add(a, value);
        }

        return value;
    };
}

Exemple de fibonacci récursif par défaut:

class Foo
{
  public Func<int,int> Fibonacci = (n) =>
  {
    return n > 1 ? Fibonacci(n-1) + Fibonacci(n-2) : n;
  };

  public Foo()
  {
    Fibonacci = Fibonacci.Memoize();

    for (int i=0; i<50; i++)
      Console.WriteLine(Fibonacci(i));
  }
}
9
Yannick Motton

Je ne sais pas si c'est une mauvaise forme de répondre à la même question deux fois ou non, mais pour avoir des idées pour une meilleure utilisation de ces types en général, je suggère de lire l'article MSDN de Jeremy Miller sur la programmation fonctionnelle:

Programmation fonctionnelle pour le développement .NET quotidien

6
Craig Vermeer

J'utilise une action pour bien encapsuler les opérations de base de données en cours d'exécution dans une transaction:

public class InTran
{
    protected virtual string ConnString
    {
        get { return ConfigurationManager.AppSettings["YourDBConnString"]; }
    }

    public void Exec(Action<DBTransaction> a)
    {
        using (var dbTran = new DBTransaction(ConnString))
        {
            try
            {
                a(dbTran);
                dbTran.Commit();
            }
            catch
            {
                dbTran.Rollback();
                throw;
            }
        }
    }
}

Maintenant, pour exécuter une transaction, je fais simplement

new InTran().Exec(tran => ...some SQL operation...);

La classe InTran peut résider dans une bibliothèque commune, ce qui réduit la duplication et fournit un emplacement unique pour les futurs ajustements de fonctionnalités.

6
Todd Stout

En les gardant génériques et en prenant en charge plusieurs arguments, cela nous permet d'éviter d'avoir à créer des délégués typés forts ou des délégués redondants qui font la même chose.

2
Hasani Blackwell

En fait, j'ai trouvé cela sur stackoverflow (au moins - l'idée):

public static T Get<T>  
    (string cacheKey, HttpContextBase context, Func<T> getItemCallback)
            where T : class
{
    T item = Get<T>(cacheKey, context);
    if (item == null) {
        item = getItemCallback();
        context.Cache.Insert(cacheKey, item);
    }

    return item;
}
2
Arnis Lapsa

J'ai un formulaire séparé qui accepte un Func générique ou une action dans le constructeur ainsi que du texte. Il exécute Func/Action sur un thread séparé tout en affichant du texte dans le formulaire et en affichant une animation.

C'est dans ma bibliothèque Util personnelle, et je l'utilise chaque fois que je veux faire une opération de longueur moyenne et bloquer l'interface utilisateur de manière non intrusive.

J'ai également envisagé de mettre une barre de progression sur le formulaire, afin qu'il puisse effectuer des opérations plus longues, mais je n'en ai pas encore vraiment eu besoin.

0
Steven Evers