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


71

我可以找到的有关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();

        }
    }
}

Answers:


56

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

12
由于许多原因,这是一种非常有用的技术。例如,与switch语句不同,您可以从外部数据动态填充操作映射。另外,键不必是intstring
罗伯特·罗斯尼

在需要时此功能很强大,但请记住,至少在可以使用跳转表的实现中,switch语句通常非常快。我不能说.NET是否使用它们。
萨曼莎·布兰纳姆


15

使用linq。

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

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

的参数WhereFunc<int, bool>

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


4
Where方法的参数实际上是a Func<T, bool>,而不是Action<T, bool>
路加福音

该msdn似乎暗示它是Func <TSource,bool> msdn.microsoft.com/en-us/library/bb534803.aspx
Yannick Motton,2009年

@Yannick M.是的。但在我的示例中,其派生自T泛型List
丹尼尔·怀特

我知道,我的评论是对您的,您认为这是一个Action<>。一个Action具有returntype void的返回类型,因此假设将其用于何处是不合逻辑的。
Yannick Motton,2009年

哦,我虽然您是用TSource
Daniel A. White

14

我一直都使用ActionFunc代表。我通常使用lambda语法声明它们以节省空间,并主要使用它们来减小大型方法的大小。当我回顾我的方法时,有时相似的代码段会脱颖而出。在这种情况下,我会将类似的代码段包装到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);
}

5
有趣的是,行数相同
JustLoren

@JustLoren,动作越大,线条越向下。但这毕竟不是重点,您的维护噩梦也更少了,这才是真正的交易。
nawfal

9

我使用它的一件事是缓存昂贵的方法调用,在相同的输入下永远不会改变:

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

1
我认为这里存在一个错误:fib不应自行调用它,还是您要用它来调用该属性(这是对自身的委托引用?)
Joel Coehoorn

1
它确实应该呼叫代表。否则,您将无法拦截递归调用。这个想法是封装调用,因此缓存实际上变得有用。
Yannick Motton,2009年


6

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


1
对不起,您可以再详细一点。?这样使用的意义何在?
shanmugharaj


2

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

不,不幸的是我不能。这是那些“技巧和窍门”问题之一。
09年

这些是泛型,不是Func或Action。不同的动物。
亚历克斯

4
有时我想知道。人们在发布之前会阅读吗?@Alex,再次检查功能参数。
09年

0

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

它在我的个人Util库中,每当我想执行中等长度的操作并以非介入方式阻止UI时,我都会使用它。

我也考虑过在表单上放置一个进度条,以便它可以执行更长的运行时间,但实际上我还不需要它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.