Action/Func vs Methods, какой смысл?

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

Это исключает, когда Action или Func используется как аргумент для того, что я не контролирую, например LINQ .Where.

Итак, в основном мой вопрос... почему они существуют? Что они дают мне лишнее и новое, что простой метод не делает?

Ответ 1

Action и Func являются предоставляемыми инфраструктурой Delegate. Делегаты позволяют обрабатывать функции как переменные, что означает, что вы можете (среди прочего) передавать их от метода к методу. Если вы когда-либо программировали на С++, вы можете думать о делегатах как о указателях функций, которые ограничены сигнатурой метода, на который они ссылаются.

Action и Func специально представляют собой общие делегаты (что означает, что они принимают параметры типа) с некоторыми из наиболее распространенных сигнатур - почти любой метод в большинстве программ может быть представлен с использованием одного или другого из этих двух, экономя людей много времени вручную определяя делегатов, как мы это делали в .net до версии 2. На самом деле, когда я вижу такой код в проекте, я могу с уверенностью предположить, что проект был перенесен из .net 1.1:

// This defines a delegate (a type that represents a function)
// but usages could easily be replaced with System.Action<String>
delegate void SomeApplicationSpecificName(String someArgument);

Я бы порекомендовал вам еще раз взглянуть на делегатов. Это очень мощная функция языка С#.

Ответ 2

Я думаю, что другие ответы здесь говорят о том, что такое Action/Func и его использовании. Я попытаюсь ответить, как выбрать между Action/Func и методом. Сначала отличия:

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

2). Методы могут иметь перегрузки (те же имена функций с разными сигнатурами), но не делегаты Action/Func, поскольку они объявлены как переменные, а с помощью правил С# у вас нет двух переменных с одно и то же имя в заданной области.

bool IsIt() { return 1 > 2; }
bool IsIt(int i) { return i > 2; } //legal

Func<bool> IsIt = () => 1 > 2; 
Func<int, bool> IsIt = i => i > 2; //illegal, duplicate variable naming

3) Следовательно, Action/Func переназначаемы и могут указывать на любую функцию, тогда как методы, которые были скомпилированы, остаются неизменными навсегда. Семантически неправильно использовать Func/Action, если метод, который он указывает, никогда не изменяется во время выполнения.

bool IsIt() { return 1 > 2; } //always returns false

Func<bool> IsIt = () => 1 > 2; 
IsIt = () => 2 > 1; //output of IsIt depends on the function it points to.

4) Вы можете указать параметры ref/out для обычных методов. Например, вы можете иметь

bool IsIt(out string p1, ref int p2) { return 1 > 2; } //legal

Func<out string, ref int, bool> IsIt; //illegal

5). Вы не можете вводить новый параметр родового типа для Action/Func (они уже являются базовыми btw, но аргументы типа могут быть только известным типом или типами, указанными в родительском метод или класс), в отличие от методов.

bool IsIt<A, R>() { return 1 > 2; } //legal

Func<bool> IsIt<A, R> = () => 1 > 2; //illegal

6) Методы могут иметь необязательные параметры, а не Action/Func.

bool IsIt(string p1 = "xyz") { return 1 > 2; } //legal

Func<string, bool> IsIt = (p1 = "xyz") => 1 > 2; //illegal

7). Вы можете иметь ключевое слово params для параметров метода, а не с помощью Action/Func.

bool IsIt(params string[] p1) { return 1 > 2; } //legal

Func<params string[], bool> IsIt = p1 => 1 > 2; //illegal

8) Intellisense хорошо играет с именами параметров методов (и, соответственно, у вас есть классная документация по XML для методов), а не с Action/Func. Что касается удобочитаемости, то выигрывают обычные методы.

9) Action/Func имеют ограничение параметра 16 (не то, что вы не можете определить свои собственные больше), но методы поддерживают больше, чем вам когда-либо понадобится.

Что касается того, когда использовать это, я бы рассмотрел следующее:

  • Когда вы вынуждены использовать его на основе любого из вышеперечисленных пунктов, у вас в любом случае нет другого выбора. Точка 3 является наиболее убедительной, на которую я нахожу, на которой вы должны будете основывать свое решение.

  • В большинстве обычных случаев обычный способ - это путь. Это стандартный способ реорганизации набора общих функций в мире С# и VB.NET.

  • Как правило, если функция больше, чем строка, я предпочитаю метод.

  • Если функция не имеет значения вне определенного метода, и функция слишком тривиальна, как простой селектор (Func<S, T>) или предикат (Func<bool>), я бы предпочел Action/Func, Например,

    public static string GetTimeStamp() 
    {
        Func<DateTime, string> f = dt => humanReadable 
                                       ? dt.ToShortTimeString() 
                                       : dt.ToLongTimeString();
        return f(DateTime.Now);
    }
    
  • Могут быть ситуации, когда Action/Func имеет больше смысла. Например, если вам нужно построить тяжелое выражение и скомпилировать делегат, его стоит сделать это только один раз и кэшировать скомпилированный делегат.

    public static class Cache<T> 
    { 
        public static readonly Func<T> Get = GetImpl();
    
        static Func<T> GetImpl()
        {
            //some expensive operation here, and return a compiled delegate
        }
    }
    

    вместо

    public static class Cache<T> 
    {
        public static T Get()
        {
            //build expression, compile delegate and invoke the delegate
        }
    }
    

    В первом случае, когда вы вызываете Get, GetImpl выполняется только один раз, где, как и во втором случае, каждый раз будет вызываться (дорогой) Get.


Не забыть, что анонимный метод сам будет определенных ограничений, не связанный с Func/Action, делая использование немного другим. Также см. для соответствующего вопроса.

Ответ 3

Я использую их для создания массива функций. Например, у меня может быть ComboBox полный действий, которые можно было бы предпринять. Я заполняю ComboBox элементами класса или структуры:

public class ComboBoxAction
{
    private string text;
    private Action method;

    public ComboBoxAction(string text, Action method)
    {
        this.text = text;
        this.method = method;
    }

    public override string ToString()
    {
        return this.text;
    }

    public void Go()
    {
        this.method();
    }
}

Затем, когда кто-то выбирает элемент, я могу вызвать действие.

CType(ComboBox1.SelectedItem, ComboBoxAction).Go()

Это намного проще, чем оператор Select определить, какой метод вызывать на основе текста ComboBox.

Ответ 4

Есть много случаев, когда Func может помочь там, где метод не будет.

public void DoThing(MyClass foo, Func<MyClass, string> func)
{
    foo.DoSomething;
    var result = func(foo);
    foo.DoStringThing(result);
}

Таким образом, вы можете указать другой Func всякий раз, когда вы вызываете этот метод - метод DoThing не должен знать, что делается, только то, что бы оно ни было, вернет строку.

Вы можете сделать это, не используя ключевое слово Func вместо ключевого слова delegate; он работает очень точно так же.

Ответ 5

Большое использование action и func - это когда нам нужно выполнить некоторую операцию (до или после метода), независимо от метода. Например, нам нужно повторить метод 10 раз, если произойдет исключение.

Рассмотрим следующий метод: его тип возврата generic. Поэтому его можно применять на func с любым возвращаемым типом.

public static T ExecuteMultipleAttempts<T>(Func<T> inputMethod, Action additionalTask, int wait, int numOfTimes)
        {
            var funcResult = default(T);
            int counter = 0;
            while (counter < numOfTimes)
            {
                try
                {
                    counter++;
                    funcResult = inputMethod();

                    //If no exception so far, the next line will break the loop.
                    break;
                }
                catch (Exception ex)
                {
                    if (counter >= numOfTimes)
                    {
                        //If already exceeded the number of attemps, throw exception
                        throw;
                    }
                    else
                    {
                        Thread.Sleep(wait);
                    }

                    if (additionalTask != null)
                    {
                        additionalTask();
                    }
                }
            }

            return funcResult;
        }