Профессионалы к локальному:
Недостатки к локальному:
Профессионалы к центральному:
Недостатки к центральному:
, я уверен, что существует больше, но они приходят на ум сразу же.
Они также удобны для рефакторинга операторов 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);
}
Используя linq.
List<int> list = { 1, 2, 3, 4 };
var even = list.Where(i => i % 2);
Параметр для Где
- это Func
.
Лямбда-выражения - одна из моих любимых частей C #. :)
I постоянно используйте делегаты 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);
}
Я использую его для кэширования вызовов дорогостоящих методов, которые никогда не меняются при одном и том же вводе:
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));
}
}
Сохраняя их общие и поддерживающие несколько аргументов, это позволяет нам избежать создания строго типизированных делегатов или избыточных делегатов, которые делают то же самое.
На самом деле, я нашел это в 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;
}
У меня есть отдельная форма, которая принимает в конструкторе общий Func или Action, а также некоторый текст. Он выполняет Func / Action в отдельном потоке, одновременно отображая некоторый текст в форме и показывая анимацию.
Он находится в моей личной библиотеке Util, и я использую его всякий раз, когда хочу выполнить операцию средней продолжительности и заблокировать пользовательский интерфейс в ненавязчивым способом.
Я также подумал о том, чтобы добавить в форму индикатор выполнения, чтобы он мог выполнять более длительные операции, но мне это пока не нужно.
Не знаю, плохо ли отвечать на один и тот же вопрос дважды или нет, но чтобы получить некоторые идеи для лучшего использования этих типов в целом, я предлагаю прочитать статью Джереми Миллера MSDN о функциональном программировании:
Функциональное программирование для повседневной разработки .NET
Я использую 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 могут находиться в общей библиотеке, что сокращает дублирование и предоставляет отдельное место для будущих корректировок функциональности.