How do you use Func<> and Action<> when designing applications?

≯℡__Kan透↙ 提交于 2019-11-29 18:45:47
Craig Vermeer

They're also handy for refactoring switch statements.

Take the following (albeit simple) example:

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

With an Action delegate, you can refactor it as follows:

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

Using linq.

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

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

The parameter for Where is an Func<int, bool>.

Lambda expressions are one of my favorite parts of C#. :)

Steve

I use the Action and Func delegates all the time. I typically declare them with lambda syntax to save space and use them primarily to reduce the size of large methods. As I review my method, sometimes code segments that are similar will stand out. In those cases, I wrap up the similar code segments into Action or Func. Using the delegate reduces redundant code, give a nice signature to the code segment and can easily be promoted to a method if need be.

I used to write Delphi code and you could declare a function within a function. Action and Func accomplish this same behavior for me in c#.

Here's a sample of repositioning controls with a delegate:

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

One thing I use it for is Caching of expensive method calls that never change given the same input:

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

The default recursive fibonacci example:

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

Dunno if it's bad form to answer the same question twice or not, but to get some ideas for better uses of these types in general I suggest reading Jeremy Miller's MSDN article on Functional Programming:

Functional Programming for Everyday .NET Development

I use an Action to nicely encapsulate executing database operations in a transaction:

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

Now to execute in a transaction I simply do

new InTran().Exec(tran => ...some SQL operation...);

The InTran class can reside in a common library, reducing duplication and provides a singe location for future functionality adjustments.

Hasani Blackwell

By keeping them generic and supporting multiple arguments, it allows us to avoid having to create strong typed delegates or redundant delegates that do the same thing.

Arnis Lapsa

Actually, i found this at stackoverflow (at least - the idea):

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

I have a separate form that accepts a generic Func or an Action in the constructor as well as some text. It executes the Func/Action on a separate thread while displaying some text in the form and showing an animation.

It's in my personal Util library, and I use it whenever I want to do a medium length operation and block the UI in a non-intrusive way.

I considered putting a progress bar on the form as well, so that it could perform longer running operations but I haven't really needed it to yet.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!