¿Cómo se utiliza Func< > y Action< > al diseñar aplicaciones?


Todos los ejemplos que puedo encontrar sobre Func y Action son simples como en el siguiente donde se ve cómo funcionan técnicamente, pero me gustaría verlos utilizados en ejemplos donde resuelven problemas que anteriormente no se podían resolver o solo se podían resolver de una manera más compleja, es decir, sé cómo funcionan y puedo ver que son concisos y poderosos, así que quiero entenderlos en un mayor sentido de qué tipo de problemas resuelven y cómo podría utilícelos en el diseño de aplicaciones.

¿De qué maneras (patrones) utiliza Func y Action para resolver problemas reales?

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

        }
    }
}
Author: Edward Tanguay, 2009-10-08

9 answers

También son útiles para refactorizar sentencias switch.

Tome el siguiente ejemplo (aunque simple):

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

Con un delegado de acción, puede refactorizarlo de la siguiente manera:

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
Author: Craig Vermeer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-23 22:59:42

Usando linq.

List<int> list = { 1, 2, 3, 4 };

var even = list.Where(i => i % 2);

El parámetro para Where es un Func<int, bool>.

Las expresiones Lambda son una de mis partes favoritas de C#. :)

 15
Author: Daniel A. White,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-08 12:09:52

Uso los delegados Action y Func todo el tiempo. Normalmente los declaro con sintaxis lambda para ahorrar espacio y los uso principalmente para reducir el tamaño de métodos grandes. A medida que reviso mi método, a veces los segmentos de código que son similares se destacarán. En esos casos, envuelvo los segmentos de código similares en Action o Func. El uso del delegado reduce el código redundante, da una firma agradable al segmento de código y se puede promover fácilmente a un método si es necesario.

Solía escribir Delphi código y usted podría declarar una función dentro de una función. Acción y Func logran este mismo comportamiento para mí en c#.

Aquí hay una muestra de controles de reposicionamiento con un delegado:

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
Author: Steve,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-27 12:33:34

Una cosa para la que lo uso es el almacenamiento en caché de llamadas a métodos costosos que nunca cambian con la misma entrada:

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

El ejemplo recursivo predeterminado de fibonacci:

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
Author: Yannick Motton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-21 12:23:06

No sé si es mala forma de responder la misma pregunta dos veces o no, pero para obtener algunas ideas para un mejor uso de estos tipos en general, sugiero leer el artículo de MSDN de Jeremy Miller sobre Programación Funcional:

Programación Funcional para el Desarrollo Diario de. NET

 6
Author: Craig Vermeer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-08 20:55:51

Utilizo una Acción para encapsular bien la ejecución de operaciones de base de datos en una transacción:

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

Ahora para ejecutar una transacción simplemente hago

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

La clase InTran puede residir en una biblioteca común, lo que reduce la duplicación y proporciona una ubicación única para futuros ajustes de funcionalidad.

 6
Author: Todd Stout,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-21 01:28:32

Manteniéndolos genéricos y soportando múltiples argumentos, nos permite evitar tener que crear delegados mecanografiados fuertes o delegados redundantes que hagan lo mismo.

 2
Author: Hasani Blackwell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-29 20:20:29

En realidad, encontré esto en stackoverflow (al menos-la idea):

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
Author: Arnis Lapsa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-27 12:32:43

Tengo una forma separada que acepta un Func genérico o una Acción en el constructor, así como algo de texto. Ejecuta el Func / Action en un hilo separado mientras muestra algo de texto en el formulario y muestra una animación.

Está en mi biblioteca personal Util, y lo uso siempre que quiero hacer una operación de longitud media y bloquear la interfaz de usuario de una manera no intrusiva.

También consideré poner una barra de progreso en el formulario, para que pudiera realizar operaciones más largas, pero todavía no lo he necesitado.

 0
Author: Steven Evers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-08 15:20:32