在设计应用程序时如何使用 Func < > 和 Action < > ?

我能找到的所有关于 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();


}
}
}
32149 次浏览

用 Linq。

List<int> list = { 1, 2, 3, 4 };


var even = list.Where(i => i % 2);

Where的参数是 Func<int, bool>

Lambda 表达式是 C # . :)中我最喜欢的部分之一

实际上,我是在 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;
}

我使用它的一个目的是缓存那些在输入相同的情况下永远不会改变的昂贵的方法调用:

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

默认的递归 fibonacci 示例:

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

通过保持它们的通用性和支持多个参数,我们可以避免创建执行相同操作的强类型委托或冗余委托。

我一直使用 ActionFunc委托。我通常使用 lambda 语法声明它们以节省空间,并主要使用它们来减少大型方法的大小。当我回顾我的方法时,有时相似的代码段会突出出来。在这些情况下,我将类似的代码段封装到 ActionFunc中。使用委托可以减少冗余代码,为代码段提供一个很好的签名,并且在需要时可以很容易地提升为一个方法。

我曾经写过 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);
}

它们对于重构 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);
}

我有一个单独的表单,它接受构造函数中的泛型 Func 或 Action 以及一些文本。它在一个单独的线程上执行 Func/Action,同时在表单中显示一些文本并显示动画。

它位于我的个人 Util 库中,当我想执行一个中等长度的操作并以非侵入的方式阻塞 UI 时,我就会使用它。

我还考虑在表单上放一个进度条,这样它就可以执行更长时间的运行操作,但我还没有真正需要它。

我使用 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 类可以驻留在一个公共库中,减少重复,并为将来的功能调整提供一个单独的位置。

不知道回答同一个问题两次或不回答是否不好,但是为了更好地使用这些类型,我建议阅读 Jeremy Miller 关于函数式编程的 MSDN 文章:

日常.NET 开发中的函数式编程