Conceptually what does an Action<Action<T>> represent
I understand that an 开发者_C百科Action represents an action to be performed on an object, however in a few code bases I have seen parameters declared as Action<Action<T>>
and struggle to understand what an action to be performed on another action means.
So conceptually what does an Action<Action<T>>
represent, and can you suggest an example where you would use one ?
Starting from the end, I have used this pattern to create a multithreaded "progress" dialog while long tasks run on the main thread. I call my progress dialog creating function with a lambda that does the work, which receives a lambda that knows how to update the progress bar and status text. Something like this:
LongRunningOperation.Run((update)=>
{
update("phase 1");
// do work for phase 1
update("phase 2");
// do work for phase 2
});
So my Run
function is declared like this:
public static void Run(Action<Action<string>> worker);
Generalizing, any worker lambda that receives a lambda from the function itself would use this pattern.
An Action<Action<T>>
is a delegate which takes an Action<T>
as its argument. This would allow you to pass around a delegate, which, when called, would get passed a second delegate (of type Action<T>
).
Usages of this would (or should) be quite rare, however. Typically, you can just use a single Action<T>
and pass that around. The only place I could see this being used would be a scenario such as a storing a set of delegates inside of a collection.
For example, suppose you had a series of operations that worked on a Person
class. This could be defined as: List<Action<Person>> actions;
Given this, if you wanted to use List<T>.ForEach
to execute all of these methods upon some specific person, provided some criteria fit, you could do something like:
Person person = GetPersion();
actions.ForEach( action =>
{
if (person.Foo)
action(person); // Call the action on the person
});
The above lambda would be an Action<Action<Person>>
. (I would personally not write code like this, however, and would instead recommend a normal foreach loop - this was just for illustration purposes to demonstrate how this could arise...)
Instead of thinking of it as an action to be performed on another action, think of it as an action that takes another action as a parameter. For example, you may have an action thats job is to enqueue another action for executing at a later time:
class WorkProcessor
{
public Action<Action<WorkItem>> WorkScheduler { get; set; }
public void ScheduleWork(WorkItem workItem)
{
WorkScheduler(ProcessWork);
}
public void ProcessWork(workItem)
{
//...
}
}
An Action<Action<T>>
is a (delegate to a) function that takes another (delegate to a) function as its argument. Such a "higher order function" can then invoke its argument multiple times, conditionally, with logging, etc. Such a function allows you to encapsulate program logic and pass it around.
Here's a simple example, in which the choice of whether or not to log function calls is made in Main(), and the implementation of that choice is then invisible to to the actual worker function; instead of passing the configuration flag around, you pass a higher order function that encapsulates the choice.
void Run( Action<int> fn, int x )
{
fn(x);
}
void RunAndLog( Action<int> fn, int x )
{
print( "before " + x );
fn(x);
print( "after " + x );
}
void InvokeWorkerTenTimes( Action<Action<int>> gn, Action<int> fn )
{
// fn is WHAT to do
// gn is HOW to do it!
for( int i=0; i<10; i++ )
gn(fn, i);
}
void DoWork(int x)
{
}
void Main()
{
if( LoggingEnabled )
InvokeWorkerTenTimes( RunAndLog, DoWork );
else
InvokeWorkerTenTimes( Run, DoWork );
}
精彩评论