Action / Func vs Методы, какой смысл? - PullRequest
31 голосов
/ 03 октября 2011

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

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

Так что в основном мой вопрос ... почему они существуют? Что они дают мне дополнительное и новое, чего не может простой метод?

Ответы [ 5 ]

30 голосов
/ 29 июня 2013

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

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

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

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 (они, кстати, уже являются универсальными, но аргументы типа могутбыть только известным типом или типами, указанными в родительском методе или классе), в отличие от методов.

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 (не то, что вы не можете определить свои собственные)с более), но методы поддерживают больше, чем вам когда-либо понадобится.

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

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

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

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

  4. Если у функции нет релевантности вне определенного метода и она слишком тривиальна, как простой селектор (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);
    }
    
  5. Могут быть ситуации, когда 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, что делает использование немного другим,Также см. по этому вопросу.

23 голосов
/ 03 октября 2011

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

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

. Я бы порекомендовал вам еще раз изучить делегатов.Они являются чрезвычайно мощной функцией языка C #.

2 голосов
/ 03 октября 2011

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

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

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

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

2 голосов
/ 03 октября 2011

Я использую их для создания массива функций.Например, у меня может быть 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();
    }
}

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

1 голос
/ 30 декабря 2016

Отличное использование 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;
        }
...