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

Posted

技术标签:

【中文标题】在设计应用程序时如何使用 Func<> 和 Action<>?【英文标题】:How do you use Func<> and Action<> when designing applications? 【发布时间】:2010-12-04 23:26:08 【问题描述】:

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

        
    

【问题讨论】:

【参考方案1】:

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

【讨论】:

这是一种非常有用的技术,原因有很多。例如,与 switch 语句不同,您可以从外部数据动态填充操作映射。此外,密钥不必是 intstring 这在需要时非常强大,但请记住 switch 语句通常非常快,至少在可以使用跳转表的实现中是这样。我不能说 .NET 是否使用它们。 这可能有助于akshaya-m.blogspot.com/2015/03/…【参考方案2】:

我一直使用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);

【讨论】:

有趣的是,同样的行数 @JustLoren 动作越大,线条越往下。但无论如何,这无关紧要,您的维护噩梦更少,这是真正的交易。【参考方案3】:

实际上,我在 *** 发现了这个(至少 - 想法):

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。不同的动物。 有时我想知道。人们在发帖之前会阅读吗? @Alex,再次检查函数参数。【参考方案4】:

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

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

【讨论】:

我认为这里有一个错误:fib 不应该调用自身,还是您的意思是调用属性(这是对自身的委托引用?) 它确实应该调用委托。否则你将无法拦截递归调用。想法是封装调用,所以缓存实际上变得有用了。【参考方案5】:

通过保持它们的通用性并支持多个参数,我们可以避免创建强类型委托或冗余委托来做同样的事情。

【讨论】:

【参考方案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 类可以驻留在公共库中,从而减少重复并为未来的功能调整提供单一位置。

【讨论】:

对不起,您能详细说明一下。 ?这样使用有什么意义?【参考方案7】:

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

Functional Programming for Everyday .NET Development

【讨论】:

【参考方案8】:

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

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

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

【讨论】:

【参考方案9】:

使用 linq。

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

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

Where 的参数是Func&lt;int, bool&gt;

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

【讨论】:

Where 方法的参数实际上是Func&lt;T, bool&gt;,而不是Action&lt;T, bool&gt; msdn 似乎暗示它是一个 Funcmsdn.microsoft.com/en-us/library/bb534803.aspx @Yannick M. True。但在我的示例中,它源自通用 List 中的 T 我知道,我的评论是让您认为它是Action&lt;&gt;Action 的返回类型为 void,因此假设它将用于 where 是不合逻辑的。 哦,我虽然你指的是TSource

以上是关于在设计应用程序时如何使用 Func<> 和 Action<>?的主要内容,如果未能解决你的问题,请参考以下文章

我可以在 Action 或 Func 代表中使用参数吗?

如何使用自动映射器映射表达式<func<Entity,DTO>>

C# 如何将 Expression<Func<SomeType>> 转换为 Expression<Func<OtherType>>

如何使用 AutoMock 模拟 Func<T> 工厂依赖项以返回不同的对象?

如何在 WCF 中使用 DataContract/DataMember 序列化 Func<int, int>(甚至是一般委托)类型字段

c# 使用运行时泛型类型调用委托