Как вы используете функции и действия при разработке приложений?
все примеры, которые я могу найти о 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();
}
}
}
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; } }
С помощью делегата действия можно выполнить его рефакторинг следующим образом:
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);
параметр
Where
этоFunc<int, bool>
.лямбда-выражения являются одной из моих любимых частей C#. :)
я использую
Action
иFunc
делегаты все время. Я обычно объявляю их с лямбда-синтаксисом, чтобы сэкономить место и использовать их в первую очередь для уменьшения размера больших методов. Когда я просматриваю свой метод, иногда выделяются сегменты кода, которые похожи. В этих случаях я обертываю аналогичные сегменты кода вAction
илиFunc
. Использование делегата уменьшает избыточный код, дает хорошую подпись сегменту кода и при необходимости может быть легко повышен до Метода.I используется для написания кода Delphi, и вы можете объявить функцию внутри функции. Действие и функция выполняют это же поведение для меня в 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)); } }
Не знаю, плохо ли отвечать на один и тот же вопрос дважды или нет, но чтобы получить некоторые идеи для лучшего использования этих типов в целом, я предлагаю прочитать статью Джереми Миллера MSDN о функциональном программировании:
функциональное программирование для повседневной разработки .NET
Я использую действие, чтобы красиво инкапсулировать выполнение операций с базой данных в транзакции:
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 может находиться в общей библиотеке, сокращая дублирование и обеспечивая место опаления для будущих корректировок функциональности.
сохраняя их универсальными и поддерживая несколько аргументов, это позволяет нам избежать необходимости создавать сильные типизированные делегаты или избыточные делегаты, которые делают то же самое.
на самом деле, я нашел это в 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; }
У меня есть отдельная форма, которая принимает общую функцию или действие в конструкторе, а также некоторый текст. Он выполняет функцию / действие в отдельном потоке, отображая некоторый текст в форме и показывая анимацию.
Он находится в моей личной библиотеке Util, и я использую его всякий раз, когда хочу выполнить операцию средней длины и заблокировать пользовательский интерфейс ненавязчивым способом.
Я также подумал о том, чтобы поместить индикатор выполнения в форму, чтобы он мог работать дольше операции, но мне это еще не нужно.