Как вы используете Func ‹› и Action ‹› при разработке приложений?

Все примеры, которые я могу найти о Func ‹> и Action‹>, просты, как в приведенном ниже, где вы видите, как они технически работают, но я бы хотел, чтобы они использовались. в примерах, где они решают проблемы, которые ранее не могли быть решены или могли быть решены только более сложным способом, т. е. я знаю, как они работают, и я вижу, что они краткие и мощные, поэтому я хочу понимать их в более широком смысле того, какие проблемы они решают и как я могу использовать их при разработке приложений.

Какими способами (шаблонами) вы используете Func ‹> и Action‹> для решения реальных проблем?

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

        }
    }
}

person Edward Tanguay    schedule 08.10.2009    source источник


Ответы (9)


Они также удобны для рефакторинга операторов switch.

Возьмем следующий (хотя и простой) пример:

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

С помощью делегата Action вы можете реорганизовать его следующим образом:

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);
}
person Craig Vermeer    schedule 08.10.2009
comment
Это невероятно полезный метод по многим причинам. В отличие от операторов switch, например, вы можете динамически заполнять карты действий из внешних данных. Кроме того, ключ не обязательно должен быть int или string. - person Robert Rossney; 08.10.2009
comment
Это мощный инструмент, когда это необходимо, но помните, что операторы переключения часто бывают очень быстрыми, по крайней мере, в реализациях, где можно использовать таблицы переходов. Я не могу сказать, использует ли их .NET или нет. - person Samantha Branham; 21.10.2009
comment
Это может помочь akshaya -m.blogspot.com/2015/03/ - person Akxaya; 24.11.2016

Используя linq.

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

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

Параметр для Where - Func<int, bool>.

Лямбда-выражения - одна из моих любимых частей C #. :)

person Daniel A. White    schedule 08.10.2009
comment
Параметром для метода Where на самом деле является Func<T, bool>, а не Action<T, bool>. - person LukeH; 08.10.2009
comment
Кажется, что msdn предполагает, что это Func ‹TSource, bool› msdn.microsoft. ru / en-us / library / bb534803.aspx - person Yannick Motton; 08.10.2009
comment
@ Янник М. Верно. Но в моем примере это происходит от T в общем List. - person Daniel A. White; 08.10.2009
comment
Я знаю, мой комментарий был для вас, думая, что это Action<>. Action имеет тип возврата void, поэтому нелогично предполагать, что он будет использоваться в where. - person Yannick Motton; 08.10.2009
comment
О, я думал, ты имел в виду это с TSource - person Daniel A. White; 08.10.2009
comment
@leppie - Верно, но многие компиляторы сегодня имеют это внутренне. - person Daniel A. White; 08.10.2009

Я постоянно использую делегатов Action и Func. Я обычно объявляю их с помощью лямбда-синтаксиса, чтобы сэкономить место, и использую их в первую очередь для уменьшения размера больших методов. Когда я просматриваю свой метод, иногда выделяются похожие сегменты кода. В таких случаях я сворачиваю похожие сегменты кода в Action или Func. Использование делегата уменьшает избыточный код, дает хорошую подпись для сегмента кода и может быть легко преобразован в метод, если это необходимо.

Раньше я писал код Delphi, и вы могли объявить функцию внутри функции. Action и Func обеспечивают то же поведение для меня в C #.

Вот пример изменения положения элементов управления с делегатом:

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);
}
person Steve    schedule 08.10.2009
comment
Интересно, что такое же количество строк - person JustLoren; 09.10.2009
comment
@JustLoren, чем больше становится действие, тем больше сходятся линии. Но в любом случае это не имеет значения, у вас меньше кошмаров, связанных с обслуживанием, а это настоящая сделка. - person nawfal; 13.12.2013

Одна вещь, для которой я его использую, - это кеширование вызовов дорогостоящих методов, которые никогда не меняются при одном и том же вводе:

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

Пример рекурсивного фибоначчи по умолчанию:

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));
  }
}
person Yannick Motton    schedule 08.10.2009
comment
Я думаю, здесь есть ошибка: не следует ли fib вызывать себя, или вы имеете в виду, что это вызывает свойство (которое является ссылкой делегата на себя?) - person Joel Coehoorn; 09.10.2009
comment
Он действительно должен вызвать делегата. В противном случае вы не смогли бы перехватить рекурсивные вызовы. Идея состоит в том, чтобы инкапсулировать вызов, чтобы кеш действительно стал полезным. - person Yannick Motton; 09.10.2009

Не знаю, плохо ли отвечать на один и тот же вопрос дважды или нет, но чтобы получить некоторые идеи для лучшего использования этих типов в целом, я предлагаю прочитать статью Джереми Миллера MSDN о функциональном программировании:

Функциональное программирование для повседневной разработки .NET

person Craig Vermeer    schedule 08.10.2009

Я использую Action, чтобы красиво инкапсулировать выполнение операций с базой данных в транзакции:

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

Теперь, чтобы выполнить транзакцию, я просто делаю

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

Класс InTran может находиться в общей библиотеке, что сокращает дублирование и предоставляет отдельное место для будущих корректировок функциональности.

person Todd Stout    schedule 08.10.2009
comment
извините, не могли бы вы рассказать больше. ? Какое значение имеет такое использование? - person shanmugharaj; 25.10.2013

Сохраняя их общие и поддерживающие несколько аргументов, это позволяет нам избежать создания строго типизированных делегатов или избыточных делегатов, которые делают то же самое.

person Hasani Blackwell    schedule 08.10.2009

Собственно, я нашел это в stackoverflow (по крайней мере, идея):

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;
}
person Arnis Lapsa    schedule 08.10.2009
comment
Нет. К сожалению, я не могу. Это был один из тех вопросов с подсказками и уловками. - person Arnis Lapsa; 08.10.2009
comment
Это дженерики, а не Func или Action. Другое животное. - person Alex; 08.10.2009
comment
Иногда мне интересно. Читают ли люди перед публикацией? @Alex, еще раз проверьте параметры функции. - person Arnis Lapsa; 09.10.2009

У меня есть отдельная форма, которая принимает общий Func или Action в конструкторе, а также некоторый текст. Он выполняет Func / Action в отдельном потоке, одновременно отображая некоторый текст в форме и показывая анимацию.

Он находится в моей личной библиотеке Util, и я использую его всякий раз, когда хочу выполнить операцию средней продолжительности и заблокировать пользовательский интерфейс ненавязчивым способом.

Я также подумал о том, чтобы добавить в форму индикатор выполнения, чтобы он мог выполнять более длительные операции, но мне это еще не нужно.

person Steven Evers    schedule 08.10.2009