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