In the following method I'm sending an enumeration of actions and want an array of ICommands back that call Action<object> that wrap those actions (needed for the relayCommand).
The problem is that if I do this inside the for each (or even a for loop) I get commands that always execute the first action passed in the parameters.
public static ICommand[] CreateCommands(IEnumerable<Action> actions)
{
List<ICommand> commands = new List<ICommand>();
Action[] actionArray = actions.ToArray();
// works
//commands.Add(new RelayCommand(o => { actionArray[0](); })); // (_execute = {Method = {Void <CreateCommands>b__0(System.Object)}})
//commands.Add(new RelayCommand(o => { actionArray[1](); })); // (_execute = {Method = {Void <CreateCommands>b__1(System.Object)}})
foreach (var action in actionArray)
{
// always add the same _execute member for each RelayCommand (_execute = {Method = {Void <CreateCommands>b__0(System.Object)}})
commands.Add(new RelayCommand(o => { action(); }));
}
return commands.ToArray();
}
It seems that the lambda is always reused inside the loop thinking it does the same, but it does not.
How do I overcome this situation?
How can i force the loop to threat o => { action(); } always as a new one?
Thanks!
What I tried as per suggestions, but did not help:
foreach (var action in actionArray)
{
Action<object> executeHandler = o => { action(); };
commands.Add(new RelayCommand(executeHandler));
}
What seems to work for me is:
class RelayExecuteWrapper
{
Action _action;
public RelayExecuteWrapper(Action action)
{
_action = action;
}
public void Execute(object o)
{
_action();
}
}
/// ...
foreach (var action in actionArray)
{
RelayExecuteWrapper rxw = new RelayExecuteWrapper(action);
commands.Add(new RelayCommand(rxw.Execute));
}
Code of RelayCommand:
/// <summary>
/// A command whose sole purpose is to
/// relay its functionality to other
/// objects by invoking delegates. The
/// default return value for the CanExecute
/// method is 'true'.
/// </summary>
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
This problem is reported several times a week on StackOverflow. The problem is that each new lambda created inside the loop shares the same "action" variable. The lambdas do not capture the value, they capture the variable. That is, when you say
List<Action> list = new List<Action>();
foreach(int x in Range(0, 10))
list.Add( ()=>{Console.WriteLine(x);} );
list[0]();
that of course prints "10" because that's the value of x now. The action is "write the current value of x", not "write the value that x was back when the delegate was created."
To get around this problem make a new variable:
List<Action> list = new List<Action>();
foreach(int x in Range(0, 10))
{
int y = x;
list.Add( ()=>{Console.WriteLine(y);} );
}
list[0]();
Since this problem is so common we are considering changing the next version of C# so that a new variable gets created every time through the foreach loop.
See http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/ for more details.
UPDATE: From the comments:
Every ICommand has the same methodinfo:
{ Method = {Void <CreateCommands>b__0(System.Object)}}
Yes, of course it does. The method is the same every time. I think you are misunderstanding what a delegate creation is. Look at it this way. Suppose you said:
var firstList = new List<Func<int>>()
{
()=>10, ()=>20
};
OK, we have a list of functions that return ints. The first one returns 10, the second one returns 20.
This is just the same as:
static int ReturnTen() { return 10; }
static int ReturnTwenty() { return 20; }
...
var firstList = new List<Func<int>>()
{ ReturnTen, ReturnTwenty };
Make sense so far? Now we add your foreach loop:
var secondList = new List<Func<int>>();
foreach(var func in firstList)
secondList.Add(()=>func());
OK, what does that mean? That means exactly the same thing as:
class Closure
{
public Func<int> func;
public int DoTheThing() { return this.func(); }
}
...
var secondList = new List<Func<int>>();
Closure closure = new Closure();
foreach(var func in firstList)
{
closure.func = func;
secondList.Add(closure.DoTheThing);
}
Now is it clear what is going on here? Every time through the loop you do not create a new closure and you certainly do not create a new method. The delegate you create always points to the same method, and always to the same closure.
Now, if instead you wrote
foreach(var loopFunc in firstList)
{
var func = loopFunc;
secondList.Add(func);
}
then the code we would generate would be
foreach(var loopFunc in firstList)
{
var closure = new Closure();
closure.func = loopFunc;
secondList.Add(closure.DoTheThing);
}
Now each new function in the list has the same methodinfo -- it is still DoTheThing -- but a different closure.
Now does it make sense why you are seeing your result?
You might want to also read:
What is the lifetime of a delegate created by a lambda in C#?
ANOTHER UPDATE: From the edited question:
What I tried as per suggestions, but did not help:
foreach (var action in actionArray)
{
Action<object> executeHandler = o => { action(); };
commands.Add(new RelayCommand(executeHandler)); }
}
Of course that did not help. That has exactly the same problem as before. The problem is that the lambda is closed over the single variable 'action' and not over each value of action. Moving around where the lambda is created obviously does not solve that problem. What you want to do is create a new variable. Your second solution does so by allocating a new variable by making a field of reference type. You don't need to do that explicitly; as I mentioned above the compiler will do so for you if you make a new variable interior to the loop body.
The correct and short way to fix the problem is
foreach (var action in actionArray)
{
Action<object> copy = action;
commands.Add(new RelayCommand(x=>{copy();}));
}
That way you make a new variable every time through the loop and each lambda in the loop therefore closes over a different variable.
Each delegate will have the same methodinfo but a different closure.
I'm not really sure about these closure and lambdas
You're doing higher-order functional programming in your program. You'd better learn about "these closures and lambdas" if you want to have any chance of doing so correctly. No time like the present.
I just made a working example of exactly what you're trying to do: http://ideone.com/hNcGx
interface ICommand
{
void Print();
}
class CommandA : ICommand
{
public void Print() { Console.WriteLine("A"); }
}
class CommandB : ICommand
{
public void Print() { Console.WriteLine("B"); }
}
public static void Main()
{
var actions = new List<Action>();
foreach (var command in new ICommand[]{
new CommandA(), new CommandB(), new CommandB()})
{
var commandcopy = command;
actions.Add(() => commandcopy.Print());
}
foreach (var action in actions)
action();
}
Output:
A
B
B
Does this help any?
Make a local reference to action in the scope of the loop.
foreach (var action in actionArray)
{
var myAction = action;
// always add the same _execute member for each RelayCommand (_execute = {Method = {Void <CreateCommands>b__0(System.Object)}})
commands.Add(new RelayCommand(o => { action(); }));
}
You're only ever using the first item in the actionArray array.
ie
commands.Add(new RelayCommand(o => { actionArray[0](); }));
You need to iterate through the collection of actions.
eg
public static ICommand[] CreateCommands(IEnumerable<Action> actions)
{
commands = actions.Select(o => new RelayCommand(o)).ToArray();
}
Code is freehand so might be some typos but should point you in the right idea.
Related
I want validate a request model with some ids. I try to preload all required data with a bulk request.
The problem is the RuleForEach inside my WhereAsync is called before the LoadUserGroupsAsync is done or started. I start the validation with TestValidateAsync(request).
Is there a better solution for this I have unfortunately not found any solutions for it. Also I have no access to the model from outside a RuleFor, RuleForEach, Where, ...
private readonly List<UserGroup> _userGroups;
WhenAsync(async (request, cancellationToken) => await this.LoadUserGroupsAsync(request.Items, cancellationToken), () =>
{
RuleForEach(o => o.Items).SetValidator(new UserUpdateValidator(this._userGroups));
});
private async Task<bool> LoadUserGroupsAsync(UserUpdateDto[] userUpdates, CancellationToken cancellationToken)
{
var ids = userUpdates.Select(o => o.userGroupId);
this._userGroups = await this._userGroupService.GetByIdsAsync(ids, cancellationToken);
return true;
}
public class UserUpdateValidator : AbstractValidator<UserUpdateDto>
{
public UserUpdateValidator(
UserGroup[] groups)
{
RuleFor(item => item.UserGroupId).Must(userGroupId =>
{
var group = groups.SingleOrDefault(o => o.Id == userGroupId);
if (group == null)
{
return false;
}
return true;
}).WithMessage("Group is invalid");
RuleFor(item => item.UserGroupId).Must(userGroupId =>
{
var group = groups.SingleOrDefault(o => o.Id == userGroupId);
return group.Active;
}).WithMessage("Group is inactive");
RuleFor(item => item.Password).Must((context, password) =>
{
var group = groups.SingleOrDefault(o => o.Id == context.UserGroupId);
if (group.Permissions.Contains("AllowPasswordChange"))
{
return true;
}
return false;
}).WithMessage("It is now allowed to change the password for your user");
}
}
Update 2021-04-28 - Add more Informations to example
You can use lazy loading and a wrapper object for the users. This would require calling sync methods instead of async methods to load the users, however.
You could use a Lazy<IEnumerable<User>> object, but that would probably require refactoring your child validator. I like creating a wrapper class for Lazy<IEnumerable<User>> just to make the code backwards-compatible for any other code accepting IEnumerable<T> objects:
/// <summary>
/// Represents a lazy loaded enumerable of type T
/// </summary>
public class LazyEnumerable<T> : IEnumerable<T>
{
private readonly Lazy<IEnumerable<T>> items;
/// <summary>
/// Initializes a new lazy loaded enumerable.
/// </summary>
/// <param name="itemFactory">A lambda expression that returns the items to be iterated over.</param>
public LazyEnumerable(Func<IEnumerable<T> itemFactory)
{
items = new Lazy<T>>(itemFactory);
}
/// <summary>
/// Initializes a new lazy loaded enumerable.
/// </summary>
/// <param name="itemFactory">The Lazy<T> object used to lazily retrieve the items to be iterated over.</param>
public LazyEnumerable(Lazy<IEnumerable<T>> items)
{
this.items = items;
}
public IEnumerator<T> GetEnumerator()
{
return items.Value.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return items.Value.GetEnumerator();
}
}
This is actually a general purpose class that can be used for any type. I wish .NET had this class in its base library, to be honest. I tend to copy and paste this in to every project, because it is so easy to use. In any event...
Then modify your validator class. Since you did not post enough code for your validator, I took a few guesses about the names of things and structure of your code:
public class YourValidator : AbstractValidator<X>
{
private UserService _userService;
private IEnumerable<UserGroup> _userGroups;
public YourValidator(UserService userService)
{
_userService = userService;
When((request, cancellationToken) => PreloadUserGroups(request.Items, cancellationToken), () =>
{
RuleForEach(o => o.Items).SetValidator(new UserUpdateValidator(_userGroups));
});
}
private bool PreloadUserGroups(UserUpdateDto[] userUpdates, CancellationToken cancellationToken)
{
var ids = userUpdates.Select(o => o.userGroupId);
_userGroups = new LazyEnumerable<UserGroup>(() => _userService.GetByIds(ids, cancellationToken));
return true;
}
}
This will lazy load the users, and since you pass the same object to all child validators it will load the users only once, regardless of how many times the collection is iterated.
Lastly, modify your child validator class to accept an IEnumerable<UserGroup> object instead of an array:
public class UserUpdateValidator : AbstractValidator<UserUpdateDto>
{
public UserUpdateValidator(IEnumerable<UserGroup> groups)
I'm developing an asynchronous application using WPF and MVVM, but I can't seem to get an async method to run inside my relaycommand.
I have a button on my WPF view hooked up to a relaycommand in my viewmodel, which trys to call an async method in my model to return a list of results:
/// <summary>
/// Search results
/// </summary>
private ObservableCollection<string> _searchResults = new ObservableCollection<string>();
public IList<string> SearchResults
{
get { return _searchResults; }
}
/// <summary>
/// Search button command
/// </summary>
private ICommand _searchCommand;
public ICommand SearchCommand
{
get
{
_searchCommand = new RelayCommand(
async() =>
{
SearchResults.Clear();
var results = await DockFindModel.SearchAsync(_selectedSearchableLayer, _searchString);
foreach (var item in results)
{
SearchResults.Add(item);
}
//notify results have changed
NotifyPropertyChanged(() => SearchResults);
},
() => bAppRunning); //command will only execute if app is running
return _searchCommand;
}
}
However I get the following exception when the relaycommand tries to execute:
An unhandled exception of type 'System.AggregateException' occurred in mscorlib.dll
Additional information: A Task's exception(s) were not observed either
by Waiting on the Task or accessing its Exception property. As a
result, the unobserved exception was rethrown by the finalizer thread.
I've tried a number of things in this thread to try and resolve the issue with no luck. Does anyone know how to resolve this?
Not sure where your RelayCommand is coming from (MVVM framework or custom implementation) but consider using an async version.
public class AsyncRelayCommand : ICommand
{
private readonly Func<object, Task> execute;
private readonly Func<object, bool> canExecute;
private long isExecuting;
public AsyncRelayCommand(Func<object, Task> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute ?? (o => true);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
public bool CanExecute(object parameter)
{
if (Interlocked.Read(ref isExecuting) != 0)
return false;
return canExecute(parameter);
}
public async void Execute(object parameter)
{
Interlocked.Exchange(ref isExecuting, 1);
RaiseCanExecuteChanged();
try
{
await execute(parameter);
}
finally
{
Interlocked.Exchange(ref isExecuting, 0);
RaiseCanExecuteChanged();
}
}
}
Ok so I actually managed to resolve the issue by making a change to the way I run my async method.
I changed this:
var results = await DockFindModel.SearchAsync();
To this:
var results = await QueuedTask.Run(() => DockFindModel.SearchAsync());
Although i'm a bit confused as to why I need to await Task.Run() when relaycommand is already accepting async lambda. I'm sure it'll become clear with time however.
Thanks to those who commented.
That RelayCommand class is probably accepting a parameter of type Action. Since you are passing an async lambda, we are having an async void scenario here.
Your code will be fired and forgot. Consider using an ICommand implementation that takes Func<Task> as a parameter instead of Action.
I have a .net web application that for all intents and purposes of this question is CRUD with many different domain objects.
A common theme across theses objects is the need to know which value properties have been modified as well as child domain model properties. Currently we have two different systems in place for this.
The value properties is the one I am trying to sort out with this question.
Right now the models all inherit from the PersistableModel base that has these fields and methods of note:
private readonly List<string> _modifiedProperties = new List<string>();
public virtual ModelState State { get; set; }
public IEnumerable<string> ModifiedProperties { get { return _modifiedProperties; } }
protected bool HasModifiedProperties { get { return 0 < _modifiedProperties.Count; } }
public bool WasModified(string propertyName)
{
return _modifiedProperties.Contains(propertyName);
}
public void WasModified(string propertyName, bool modified)
{
if (modified)
{
if (!WasModified(propertyName)) _modifiedProperties.Add(propertyName);
}
else
{
_modifiedProperties.Remove(propertyName);
}
}
Then within each individual model whenever a property is set we also need to call WasModified with a string of the property name and a boolean value.
Obviously this is very tedious and error prone, what I want to do is redesign this base class to automatically add entries to the dictionary when a derived class's property is set.
In my research the closest I've been able to get is to use PostSharp which is out of the question.
While working on a different project I came up with a solution that gets most of the way towards my original goal.
Note that this solution is reliant upon the Dev Express ViewModelBase as its base class, but it wouldn't be hard to make a new base class with the features being used for non Dev Express projects:
Edit: I found an issue with the reset state logic, this update removes that issue.
public abstract class UpdateableModel : ViewModelBase
{
private static readonly MethodInfo GetPropertyMethod;
private static readonly MethodInfo SetPropertyMethod;
private readonly bool _trackingEnabled;
private readonly Dictionary<string, Tuple<Expression, object>> _originalValues;
private readonly List<string> _differingFields;
static UpdateableModel()
{
GetPropertyMethod = typeof(UpdateableModel).GetMethod("GetProperty", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
SetPropertyMethod = typeof(UpdateableModel).GetMethod("SetProperty");
}
protected UpdateableModel(bool isNewModel)
{
_originalValues = new Dictionary<string, Tuple<Expression, object>>();
_differingFields = new List<string>();
if (isNewModel) return;
State = ModelState.Unmodified;
_trackingEnabled = true;
}
public ModelState State
{
get { return GetProperty(() => State); }
set { base.SetProperty(() => State, value); }
}
public new bool SetProperty<T>(Expression<Func<T>> expression, T value)
{
var wasUpdated = base.SetProperty(expression, value);
if (_trackingEnabled && wasUpdated)
{
UpdateState(expression, value);
}
return wasUpdated;
}
/// <summary>
/// Reset State is meant to be called when discarding changes, it will reset the State value to Unmodified and set all modified values back to their original value.
/// </summary>
public void ResetState()
{
if (!_trackingEnabled) return;
foreach (var differingField in _differingFields)
{
var type = GetFuncType(_originalValues[differingField].Item1);
var genericPropertySetter = SetPropertyMethod.MakeGenericMethod(type);
genericPropertySetter.Invoke(this, new[]{_originalValues[differingField].Item2});
}
}
/// <summary>
/// Update State is meant to be called after changes have been persisted, it will reset the State value to Unmodified and update the original values to the new values.
/// </summary>
public void UpdateState()
{
if (!_trackingEnabled) return;
foreach (var differingField in _differingFields)
{
var type = GetFuncType(_originalValues[differingField].Item1);
var genericPropertySetter = GetPropertyMethod.MakeGenericMethod(type);
var value = genericPropertySetter.Invoke(this, new object[] { _originalValues[differingField].Item1 });
var newValue = new Tuple<Expression, object>(_originalValues[differingField].Item1,value);
_originalValues[differingField] = newValue;
}
_differingFields.Clear();
State = ModelState.Unmodified;
}
private static Type GetFuncType(Expression expr)
{
var lambda = expr as LambdaExpression;
if (lambda == null)
{
return null;
}
var member = lambda.Body as MemberExpression;
return member != null ? member.Type : null;
}
private void UpdateState<T>(Expression<Func<T>> expression, T value)
{
var propertyName = GetPropertyName(expression);
if (!_originalValues.ContainsKey(propertyName))
{
_originalValues.Add(propertyName, new Tuple<Expression,object>(expression, value));
}
else
{
if (!Compare(_originalValues[propertyName].Item2, value))
{
_differingFields.Add(propertyName);
}
else if (_differingFields.Contains(propertyName))
{
_differingFields.Remove(propertyName);
}
State = _differingFields.Count == 0
? ModelState.Unmodified
: ModelState.Modified;
}
}
private static bool Compare<T>(T x, T y)
{
return EqualityComparer<T>.Default.Equals(x, y);
}
Another quick note, the IsNewModel constructor flag serves to differentiate objects create on the UI level which don't need any kind of state tracking, and objects generated from the data access level which will need the state tracking.
I want enqueue a list of tasks and then perform on certain event. Code:
internal class MyClass
{
private Queue<Task> m_taskQueue;
protected MyClass()
{
m_taskQueue = new Queue<Task>();
}
public delegate bool Task(object[] args);
public void EnqueueTask(Task task)
{
m_taskQueue.Enqueue(task);
}
public virtual bool Save()
{
// save by processing work queue
while (m_taskQueue.Count > 0)
{
var task = m_taskQueue.Dequeue();
var workItemResult = task.Invoke();
if (!workItemResult)
{
// give up on a failure
m_taskQueue.Clear();
return false;
}
}
return true;
}
}
Each delegate task may have their own list of parameters: Task(object[] args). My question is how to pass the parameter to each task for the task queue?
Okay, now we have a bit more information, it sounds like your EnqueueTask method should actually look like this:
public void EnqueueTask(Task task, object[] values)
Right?
For starters I would avoid using the name Task, which is already part of the core of .NET 4 and will become very prominent in .NET 5. As Joshua said, you've basically got a Func<object[], bool>.
Next, you could keep two lists - one for the delegates and one for the values, but it's easier just to keep a Queue<Func<bool>> like this:
private readonly Queue<Func<bool>> taskQueue = new Queue<Func<bool>>();
public void EnqueueTask(Task task, object[] values)
{
taskQueue.Enqueue(() => task(values));
}
Then the rest of your code will actually work "as is". The lambda expression there will capture values and task, so when you invoke the Func<bool>, it will supply those values to the original delegate.
Provided understanding your question correctly you just pass the information like a normal call. Have you considered using Func? You can just pass arguments to the Task.Invoke i.e. Task.Invoke([arguments here as a *single* object array]).
object[] arguments = null; // assign arguments to something
var workItemResult = task.Invoke(arguments);
Below is an example with the Func type.
internal class MyClass
{
private Queue<Func<object[], bool>> m_taskQueue;
protected MyClass()
{
m_taskQueue = new Queue<Func<object[], bool>>();
}
public void EnqueueTask(Func<object[], bool> task)
{
m_taskQueue.Enqueue(task);
}
public virtual bool Save()
{
object[] arguments = null; // assign arguments to something
// save by processing work queue
while (m_taskQueue.Count > 0)
{
var task = m_taskQueue.Dequeue();
var workItemResult = task(arguments);
if (!workItemResult)
{
// give up on a failure
m_taskQueue.Clear();
return false;
}
}
return true;
}
}
What do I have to do to say that InvokeMethod can invoke a method and when using special options like Repeat it shall exexute after the Repeat.
My problem for now is that the method will already exexute before it knows that it has to be called 100 times.
class Program
{
static void Main()
{
const bool shouldRun = true;
new MethodExecuter()
.ForAllInvocationsUseCondition(!Context.WannaShutDown)
.InvokeMethod(A.Process).Repeat(100)
.When(shouldRun).ThenInvokeMethod(B.Process).Repeat(10)
.ForAllInvocationsUseCondition(Context.WannaShutDown)
.When(shouldRun).ThenInvokeMethod(C.Process);
}
}
MethodExpression
public class MethodExpression
{
private bool _isTrue = true;
private readonly MethodExecuter _methodExecuter;
public MethodExpression(bool isTrue, MethodExecuter methodExecuter)
{
_isTrue = isTrue;
_methodExecuter = methodExecuter;
}
public MethodExecuter ThenInvokeMethod(Action action)
{
if (_isTrue)
{
action.Invoke();
_isTrue = false;
}
return _methodExecuter;
}
}
MethodExecuter
public class MethodExecuter
{
private bool _condition;
private int _repeat = 1;
public MethodExpression When(bool isTrue)
{
return new MethodExpression(isTrue && _condition, this);
}
public MethodExecuter InvokeMethod(Action action)
{
if (_condition)
{
for (int i = 1; i <= _repeat; i++)
{
action.Invoke();
}
}
return this;
}
public MethodExecuter ForAllInvocationsUseCondition(bool condition)
{
_condition = condition;
return this;
}
public MethodExecuter Repeat(int repeat)
{
_repeat = repeat;
return this;
}
}
Use a final method ("go", or "execute") to actually kick things off.
new MethodExecuter()
.ForAllInvocationsUseCondition(!Context.WannaShutDown)
.InvokeMethod(A.Process).Repeat(100)
.When(shouldRun).ThenInvokeMethod(B.Process).Repeat(10)
.ForAllInvocationsUseCondition(Context.WannaShutDown)
.When(shouldRun).ThenInvokeMethod(C.Process)
.Go();
What you've provided looks a bit like programming a workflow or state machine. In order to capture invocations and respect conditions during execution, you'd need to change your approach slightly.
Instead of invoking actions as they come in, consider pushing your actions into a queue and then providing an mechanism to run the state machine.
new MethodInvoker()
.ForAllInvocationsUseCondition(true)
.InvokeMethod( Process.A ).Repeat(100)
.Run();
There are a lot of ways to skin this cat, but I think one source of this difficulty is in the fact that you actually invoke the method within the InvokeMethod() method (go figure!).
Typically, we use fluent APIs to turn syntax that is evaluated from the inside-out into something that can be expressed in a left-to-right fashion. Thus, the expression builder components of the interface are used to build up state throughout the expression, and only at the end does the "real work" happen.
One solution to your immediate problem is to queue up each action with its associated options (invocation conditions, repeat count, etc.), and add some ExecuteAll() method to MethodExecuter that dequeues and executes the fully configured actions at the end of the member chain.
Another solution would be to put all of the execution options inside the InvokeMethod() method; something like:
.Invoke(x => x.Method(A.Process).Repeat(100))
This method would look something like:
public MethodExecuter Invoke(Action<IExecutionBuilder> executionBuilder)
{
var builder = new ExecutionBuilder();
executionBuilder(builder);
var action = builder.Action;
var repeat = builder.RepeatCount;
if (_condition)
{
for (int i = 1; i <= repeat; i++)
{
action();
}
}
return this;
}
I haven't worked through this in Visual Studio, but the other items would be something like:
public interface IExecutionBuilder
{
IExecutionBuilder Method(Action action);
IExecutionBuilder Repeat(int count);
}
public class ExecutionBuilder : IExecutionBuilder
{
public ExecutionBuilder()
{
RepeatCount = 1; // default to repeat once
Action = () => {}; // default to do nothing, but not null
}
public IExecutionBuilder Method(Action action)
{
Action = action;
return this;
}
public IExecutionBuilder Repeat(int repeat)
{
RepeatCount = repeat;
return this;
}
public int RepeatCount { get; private set; }
public Action Action { get; private set; }
}
Note that RepeatCount and Action are not exposed on the interface. This way, you will not see these members when calling .Invoke(x => x., but will have access to them when using the concrete ExecutionBuilder class inside the Invoke() method.
You could have a SetInvokeMethod and an Execute Method.
SetInvokeMethod(Action).Repeat(100).Execute()
In a sentence, your code is too "eager". The InvokeMethod method is called, and performs the action, before your fluent grammar structure tells your code how many times it should repeat.
To change this, try also specifying the method you are invoking as a private field in your methodInvoker, then include a command that is a "trigger" to actually perform the commands. The key is "lazy evaluation"; in a fluent interface, nothing should be done until it has to; that way you have most of the control over when it does happen.
public class FluentMethodInvoker
{
Predicate condition = ()=>true;
Predicate allCondition = null;
Action method = ()=> {return;};
bool iterations = 1;
FluentMethodInvoker previous = null;
public FluentMethodInvoker(){}
private FluentMethodInvoker(FluentMethodInvoker prevNode)
{ previous = prevNode; }
public FluentMethodInvoker InvokeMethod(Action action)
{
method = action;
}
//Changed "When" to "If"; the function does not wait for the condition to be true
public FluentMethodInvoker If(Predicate pred)
{
condition = pred;
return this;
}
public FluentMethodInvoker ForAllIf(Predicate pred)
{
allCondition = pred;
return this;
}
private bool CheckAllIf()
{
return allCondition == null
? previous == null
? true
: previous.CheckAllIf();
: allCondition;
}
public FluentMethodInvoker Repeat(int repetitions)
{
iterations = repetitions;
return this;
}
//Merging MethodExecuter and MethodExpression, by chaining instances of FluentMethodInvoker
public FluentMethodInvoker Then()
{
return new FluentMethodInvoker(this);
}
//Here's your trigger
public void Run()
{
//goes backward through the chain to the starting node
if(previous != null) previous.Run();
if(condition && CheckAllIf())
for(var i=0; i<repetitions; i++)
method();
return;
}
}
//usage
class Program
{
static void Main()
{
const bool shouldRun = true;
var invokerChain = new FluentMethodInvoker()
.ForAllIf(!Context.WannaShutDown)
.InvokeMethod(A.Process).Repeat(100)
.When(shouldRun)
.Then().InvokeMethod(B.Process).Repeat(10)
.ForAllIf(Context.WannaShutDown)
.When(shouldRun)
.Then().InvokeMethod(C.Process);
//to illustrate that the chain doesn't have to execute immediately when being built
invokerChain.Run();
}
}